Merge pull request woocommerce/woocommerce-admin#2325 from woocommerce/add/admin-note-trigger-action

Trigger an action server side when admin note actions are clicked
This commit is contained in:
Jeff Stieler 2019-06-12 17:04:10 +02:00 committed by GitHub
commit b83ef86add
10 changed files with 245 additions and 35 deletions

View File

@ -219,6 +219,14 @@
// Needs the double-class for specificity
.woocommerce-activity-card.woocommerce-inbox-activity-card {
grid-template-columns: 72px 1fr;
height: 100%;
opacity: 1;
padding: $fallback-gutter;
padding: $gutter;
@media screen and (prefers-reduced-motion: no-preference) {
transition: opacity 0.3s, height 0s, padding 0s;
}
@include breakpoint( '<782px' ) {
grid-template-columns: 64px 1fr;
@ -227,6 +235,15 @@
.woocommerce-activity-card__header {
margin-bottom: $gap-small;
}
&.actioned {
height: 0;
opacity: 0;
padding: 0;
@media screen and (prefers-reduced-motion: no-preference) {
transition: opacity 0.3s, height 0s 0.3s, padding 0s 0.3s;
}
}
}
.woocommerce-review-activity-card {

View File

@ -18,6 +18,7 @@ import { EmptyContent, Section } from '@woocommerce/components';
import sanitizeHTML from 'lib/sanitize-html';
import { QUERY_DEFAULTS } from 'wc-api/constants';
import withSelect from 'wc-api/with-select';
import classnames from 'classnames';
class InboxPanel extends Component {
constructor( props ) {
@ -49,18 +50,23 @@ class InboxPanel extends Component {
}
renderNotes() {
const { lastRead, notes } = this.props;
const { lastRead, notes, triggerNoteAction } = this.props;
if ( 0 === Object.keys( notes ).length ) {
return this.renderEmptyCard();
}
const getButtonsFromActions = actions => {
if ( ! actions ) {
const getButtonsFromActions = note => {
if ( ! note.actions ) {
return [];
}
return actions.map( action => (
<Button isDefault href={ action.url || undefined }>
return note.actions.map( action => (
<Button
isDefault
isPrimary={ action.primary }
href={ action.url || undefined }
onClick={ () => triggerNoteAction( note.id, action.id ) }
>
{ action.label }
</Button>
) );
@ -71,7 +77,9 @@ class InboxPanel extends Component {
return notesArray.map( note => (
<ActivityCard
key={ note.id }
className="woocommerce-inbox-activity-card"
className={ classnames( 'woocommerce-inbox-activity-card', {
actioned: 'unactioned' !== note.status,
} ) }
title={ note.title }
date={ note.date_created_gmt }
icon={ <Gridicon icon={ note.icon } size={ 48 } /> }
@ -80,7 +88,7 @@ class InboxPanel extends Component {
! note.date_created_gmt ||
new Date( note.date_created_gmt + 'Z' ).getTime() > lastRead
}
actions={ getButtonsFromActions( note.actions ) }
actions={ getButtonsFromActions( note ) }
>
<span dangerouslySetInnerHTML={ sanitizeHTML( note.content ) } />
</ActivityCard>
@ -145,6 +153,7 @@ export default compose(
type: 'info,warning',
orderby: 'date',
order: 'desc',
status: 'unactioned',
};
const notes = getNotes( inboxQuery );
@ -154,10 +163,11 @@ export default compose(
return { notes, isError, isRequesting, lastRead: userData.activity_panel_inbox_last_read };
} ),
withDispatch( dispatch => {
const { updateCurrentUserData } = dispatch( 'wc-api' );
const { updateCurrentUserData, triggerNoteAction } = dispatch( 'wc-api' );
return {
updateCurrentUserData,
triggerNoteAction,
};
} )
)( InboxPanel );

View File

@ -8,7 +8,6 @@ import { IconButton, Button, Dashicon, Dropdown, NavigableMenu } from '@wordpres
import classnames from 'classnames';
import interpolateComponents from 'interpolate-components';
import { compose } from '@wordpress/compose';
import { noop } from 'lodash';
import { withDispatch } from '@wordpress/data';
import moment from 'moment';
@ -64,18 +63,15 @@ class StoreAlerts extends Component {
}
renderActions( alert ) {
const { updateNote } = this.props;
const { triggerNoteAction, updateNote } = this.props;
const actions = alert.actions.map( action => {
const markStatus = () => {
updateNote( alert.id, { status: action.status } );
};
return (
<Button
key={ action.name }
isDefault
isPrimary={ action.primary }
href={ action.url || undefined }
onClick={ '' === action.status ? noop : markStatus }
onClick={ () => triggerNoteAction( alert.id, action.id ) }
>
{ action.label }
</Button>
@ -257,8 +253,10 @@ export default compose(
};
} ),
withDispatch( dispatch => {
const { updateNote } = dispatch( 'wc-api' );
const { triggerNoteAction, updateNote } = dispatch( 'wc-api' );
return {
triggerNoteAction,
updateNote,
};
} )

View File

@ -5,6 +5,12 @@ const updateNote = operations => ( noteId, noteFields ) => {
operations.update( [ resourceKey ], { [ resourceKey ]: { noteId, ...noteFields } } );
};
const triggerNoteAction = operations => ( noteId, actionId ) => {
const resourceKey = 'note-action';
operations.update( [ resourceKey ], { [ resourceKey ]: { noteId, actionId } } );
};
export default {
updateNote,
triggerNoteAction,
};

View File

@ -20,7 +20,10 @@ function read( resourceNames, fetch = apiFetch ) {
}
function update( resourceNames, data, fetch = apiFetch ) {
return [ ...updateNote( resourceNames, data, fetch ) ];
return [
...updateNote( resourceNames, data, fetch ),
...triggerAction( resourceNames, data, fetch ),
];
}
function readNoteQueries( resourceNames, fetch ) {
@ -93,7 +96,26 @@ function updateNote( resourceNames, data, fetch ) {
return [];
}
function triggerAction( resourceNames, data, fetch ) {
const resourceName = 'note-action';
if ( resourceNames.includes( resourceName ) ) {
const { noteId, actionId } = data[ resourceName ];
const url = `${ NAMESPACE }/admin/notes/${ noteId }/action/${ actionId }`;
return [
fetch( { path: url, method: 'POST' } )
.then( note => {
return { [ 'note:' + noteId ]: { data: note } };
} )
.catch( error => {
return { [ 'note:' + noteId ]: { error } };
} ),
];
}
return [];
}
export default {
read,
update,
triggerAction,
};

View File

@ -0,0 +1,114 @@
<?php
/**
* REST API Admin Note Action controller
*
* Handles requests to the admin note action endpoint.
*
* @package WooCommerce Admin/API
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Admin Note Action controller class.
*
* @package WooCommerce/API
* @extends WC_REST_CRUD_Controller
*/
class WC_Admin_REST_Admin_Note_Action_Controller extends WC_Admin_REST_Admin_Notes_Controller {
/**
* Register the routes for admin notes.
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<note_id>[\d-]+)/action/(?P<action_id>[\d-]+)',
array(
'args' => array(
'note_id' => array(
'description' => __( 'Unique ID for the Note.', 'woocommerce-admin' ),
'type' => 'integer',
),
'action_id' => array(
'description' => __( 'Unique ID for the Note Action.', 'woocommerce-admin' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'trigger_note_action' ),
// @todo - double check these permissions for taking note actions.
'permission_callback' => array( $this, 'get_item_permissions_check' ),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Trigger a note action.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Request|WP_Error
*/
public function trigger_note_action( $request ) {
$note = WC_Admin_Notes::get_note( $request->get_param( 'note_id' ) );
if ( ! $note ) {
return new WP_Error(
'woocommerce_admin_notes_invalid_id',
__( 'Sorry, there is no resource with that ID.', 'woocommerce-admin' ),
array( 'status' => 404 )
);
}
// Find note action by ID.
$action_id = $request->get_param( 'action_id' );
$actions = $note->get_actions( 'edit' );
$triggered_action = false;
foreach ( $actions as $action ) {
if ( $action->id === $action_id ) {
$triggered_action = $action;
}
}
if ( ! $triggered_action ) {
return new WP_Error(
'woocommerce_admin_note_action_invalid_id',
__( 'Sorry, there is no resource with that ID.', 'woocommerce-admin' ),
array( 'status' => 404 )
);
}
/**
* Fires when an admin note action is taken.
*
* @param string $name The triggered action name.
* @param WC_Admin_Note $note The corresponding Note.
*/
do_action( 'woocommerce_admin_note_action', $triggered_action->name, $note );
/**
* Fires when an admin note action is taken.
* For more specific targeting of note actions.
*
* @param WC_Admin_Note $note The corresponding Note.
*/
do_action( 'woocommerce_admin_note_action_' . $triggered_action->name, $note );
// Update the note with the status for this action.
if ( ! empty( $triggered_action->status ) ) {
$note->set_status( $triggered_action->status );
}
$note->save();
$data = $note->get_data();
$data = $this->prepare_item_for_response( $data, $request );
$data = $this->prepare_response_for_collection( $data );
return rest_ensure_response( $data );
}
}

View File

@ -86,7 +86,7 @@ class WC_Admin_REST_Admin_Notes_Controller extends WC_REST_CRUD_Controller {
if ( ! $note ) {
return new WP_Error(
'woocommerce_admin_notes_invalid_id',
__( 'Sorry, there is no resouce with that ID.', 'woocommerce-admin' ),
__( 'Sorry, there is no resource with that ID.', 'woocommerce-admin' ),
array( 'status' => 404 )
);
}
@ -199,7 +199,7 @@ class WC_Admin_REST_Admin_Notes_Controller extends WC_REST_CRUD_Controller {
if ( ! $note ) {
return new WP_Error(
'woocommerce_admin_notes_invalid_id',
__( 'Sorry, there is no resouce with that ID.', 'woocommerce-admin' ),
__( 'Sorry, there is no resource with that ID.', 'woocommerce-admin' ),
array( 'status' => 404 )
);
}

View File

@ -103,6 +103,7 @@ class WC_Admin_Api_Init {
*/
public function rest_api_init() {
require_once WC_ADMIN_ABSPATH . 'includes/api/class-wc-admin-rest-admin-notes-controller.php';
require_once WC_ADMIN_ABSPATH . 'includes/api/class-wc-admin-rest-admin-note-action-controller.php';
require_once WC_ADMIN_ABSPATH . 'includes/api/class-wc-admin-rest-coupons-controller.php';
require_once WC_ADMIN_ABSPATH . 'includes/api/class-wc-admin-rest-data-controller.php';
require_once WC_ADMIN_ABSPATH . 'includes/api/class-wc-admin-rest-data-countries-controller.php';
@ -141,6 +142,7 @@ class WC_Admin_Api_Init {
$controllers = array(
'WC_Admin_REST_Admin_Notes_Controller',
'WC_Admin_REST_Admin_Note_Action_Controller',
'WC_Admin_REST_Coupons_Controller',
'WC_Admin_REST_Customers_Controller',
'WC_Admin_REST_Data_Controller',

View File

@ -198,17 +198,31 @@ class WC_Admin_Notes_Data_Store extends WC_Data_Store_WP implements WC_Object_Da
private function read_actions( &$note ) {
global $wpdb;
$actions = $wpdb->get_results(
$db_actions = $wpdb->get_results(
$wpdb->prepare(
"SELECT name, label, query, status, is_primary FROM {$wpdb->prefix}wc_admin_note_actions WHERE note_id = %d",
"SELECT action_id, name, label, query, status, is_primary
FROM {$wpdb->prefix}wc_admin_note_actions
WHERE note_id = %d",
$note->get_id()
)
);
if ( $actions ) {
foreach ( $actions as $action ) {
$note->add_action( $action->name, $action->label, $action->query, $action->status, $action->is_primary );
$note_actions = array();
if ( $db_actions ) {
foreach ( $db_actions as $action ) {
$note_actions[] = (object) array(
'id' => (int) $action->action_id,
'name' => $action->name,
'label' => $action->label,
'query' => $action->query,
'status' => $action->status,
'primary' => (bool) $action->is_primary,
);
}
}
$note->set_actions( $note_actions );
}
/**
@ -220,24 +234,42 @@ class WC_Admin_Notes_Data_Store extends WC_Data_Store_WP implements WC_Object_Da
* @return bool|void
*/
private function save_actions( &$note ) {
global $wpdb;
$changed_props = array_keys( $note->get_changes() );
if ( ! in_array( 'actions', $changed_props, true ) ) {
return false;
}
global $wpdb;
$wpdb->delete( $wpdb->prefix . 'wc_admin_note_actions', array( 'note_id' => $note->get_id() ) );
foreach ( $note->get_actions( 'edit' ) as $action ) {
$wpdb->insert(
$action_data = array(
'note_id' => $note->get_id(),
'name' => $action->name,
'label' => $action->label,
'query' => $action->query,
'status' => $action->status,
'is_primary' => $action->primary,
);
$data_format = array(
'%d',
'%s',
'%s',
'%s',
'%s',
'%d',
);
if ( ! empty( $action->id ) ) {
$action_data['action_id'] = $action->id;
$data_format[] = '%d';
}
$wpdb->replace(
$wpdb->prefix . 'wc_admin_note_actions',
array(
'note_id' => $note->get_id(),
'name' => $action->name,
'label' => $action->label,
'query' => $action->query,
'status' => $action->status,
'is_primary' => $action->primary,
)
$action_data,
$data_format
);
}
}

View File

@ -485,7 +485,7 @@ class WC_Admin_Note extends WC_Data {
public function add_action( $name, $label, $url = '', $status = self::E_WC_ADMIN_NOTE_ACTIONED, $primary = false ) {
$name = wc_clean( $name );
$label = wc_clean( $label );
$query = esc_url( $url );
$query = esc_url_raw( $url );
$status = wc_clean( $status );
$primary = (bool) $primary;
@ -509,4 +509,13 @@ class WC_Admin_Note extends WC_Data {
$note_actions[] = (object) $action;
$this->set_prop( 'actions', $note_actions );
}
/**
* Set actions on a note.
*
* @param array $actions Note actions.
*/
public function set_actions( $actions ) {
$this->set_prop( 'actions', $actions );
}
}