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:
commit
b83ef86add
|
@ -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 {
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
} )
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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 )
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue