Add store notice around processing historical data. (https://github.com/woocommerce/woocommerce-admin/pull/1763)

* Add store notice around processing historical data.

* Cleanup

* Handle PR feedback.

* Clean up `add` logic and add empty content_data. Also add logic to get_notes_count so that we can hide unactioned statuses.

* Add the ability to update a note, and to also mark a status when an alert is clicked.

* Remove mark_actioned call on sync

* add missing todo
This commit is contained in:
Justin Shreve 2019-03-12 09:13:20 -04:00 committed by GitHub
parent 6c9b96f49a
commit 1348245406
20 changed files with 391 additions and 105 deletions

View File

@ -37,6 +37,38 @@ const orderStatuses = Object.keys( wcSettings.orderStatuses )
} ); } );
export const analyticsSettings = applyFilters( SETTINGS_FILTER, [ export const analyticsSettings = applyFilters( SETTINGS_FILTER, [
{
name: 'woocommerce_rebuild_reports_data',
label: __( 'Rebuild reports data:', 'wc-admin' ),
inputType: 'button',
inputText: __( 'Rebuild reports', 'wc-admin' ),
helpText: __(
'This tool will rebuild all of the information used by the reports. ' +
'Data will be processed in the background and may take some time depending on the size of your store.',
'wc-admin'
),
callback: ( resolve, reject, addNotice ) => {
const errorMessage = __( 'There was a problem rebuilding your report data.', 'wc-admin' );
apiFetch( { path: '/wc/v3/system_status/tools/rebuild_stats', method: 'PUT' } )
.then( response => {
if ( response.success ) {
addNotice( { status: 'success', message: response.message } );
// @todo This should be changed to detect when the lookup table population is complete.
setTimeout( () => resolve(), 300000 );
} else {
addNotice( { status: 'error', message: errorMessage } );
reject();
}
} )
.catch( error => {
if ( error && error.message ) {
addNotice( { status: 'error', message: error.message } );
}
reject();
} );
},
},
{ {
name: 'woocommerce_excluded_report_order_statuses', name: 'woocommerce_excluded_report_order_statuses',
label: __( 'Excluded Statuses:', 'wc-admin' ), label: __( 'Excluded Statuses:', 'wc-admin' ),
@ -89,36 +121,4 @@ export const analyticsSettings = applyFilters( SETTINGS_FILTER, [
initialValue: wcSettings.wcAdminSettings.woocommerce_actionable_order_statuses || [], initialValue: wcSettings.wcAdminSettings.woocommerce_actionable_order_statuses || [],
defaultValue: [ 'processing', 'on-hold' ], defaultValue: [ 'processing', 'on-hold' ],
}, },
{
name: 'woocommerce_rebuild_reports_data',
label: __( 'Rebuild reports data:', 'wc-admin' ),
inputType: 'button',
inputText: __( 'Rebuild reports', 'wc-admin' ),
helpText: __(
'This tool will rebuild all of the information used by the reports. ' +
'Data will be processed in the background and may take some time depending on the size of your store.',
'wc-admin'
),
callback: ( resolve, reject, addNotice ) => {
const errorMessage = __( 'There was a problem rebuilding your report data.', 'wc-admin' );
apiFetch( { path: '/wc/v3/system_status/tools/rebuild_stats', method: 'PUT' } )
.then( response => {
if ( response.success ) {
addNotice( { status: 'success', message: response.message } );
// @todo This should be changed to detect when the lookup table population is complete.
setTimeout( () => resolve(), 300000 );
} else {
addNotice( { status: 'error', message: errorMessage } );
reject();
}
} )
.catch( error => {
if ( error && error.message ) {
addNotice( { status: 'error', message: error.message } );
}
reject();
} );
},
},
] ); ] );

View File

@ -8,6 +8,8 @@ import { IconButton, Button, Dashicon } from '@wordpress/components';
import classnames from 'classnames'; import classnames from 'classnames';
import interpolateComponents from 'interpolate-components'; import interpolateComponents from 'interpolate-components';
import { compose } from '@wordpress/compose'; import { compose } from '@wordpress/compose';
import { noop } from 'lodash';
import { withDispatch } from '@wordpress/data';
/** /**
* WooCommerce dependencies * WooCommerce dependencies
@ -64,7 +66,7 @@ class StoreAlerts extends Component {
const alerts = this.props.alerts || []; const alerts = this.props.alerts || [];
const preloadAlertCount = wcSettings.alertCount && parseInt( wcSettings.alertCount ); const preloadAlertCount = wcSettings.alertCount && parseInt( wcSettings.alertCount );
if ( preloadAlertCount > 0 && 0 === alerts.length ) { if ( preloadAlertCount > 0 && this.props.isLoading ) {
return <StoreAlertsPlaceholder hasMultipleAlerts={ preloadAlertCount > 1 } />; return <StoreAlertsPlaceholder hasMultipleAlerts={ preloadAlertCount > 1 } />;
} else if ( 0 === alerts.length ) { } else if ( 0 === alerts.length ) {
return null; return null;
@ -78,11 +80,22 @@ class StoreAlerts extends Component {
'is-alert-error': 'error' === type, 'is-alert-error': 'error' === type,
'is-alert-update': 'update' === type, 'is-alert-update': 'update' === type,
} ); } );
const actions = alert.actions.map( action => (
<Button key={ action.name } isDefault href={ action.url }> const actions = alert.actions.map( action => {
{ action.label } const markStatus = () => {
</Button> this.props.updateNote( alert.id, { status: action.status } );
) ); };
return (
<Button
key={ action.name }
isDefault
href={ action.url }
onClick={ '' === action.status ? noop : markStatus }
>
{ action.label }
</Button>
);
} );
return ( return (
<Card <Card
@ -135,15 +148,29 @@ class StoreAlerts extends Component {
export default compose( export default compose(
withSelect( select => { withSelect( select => {
const { getNotes } = select( 'wc-api' ); const { getNotes, isGetNotesRequesting } = select( 'wc-api' );
const alertsQuery = { const alertsQuery = {
page: 1, page: 1,
per_page: QUERY_DEFAULTS.pageSize, per_page: QUERY_DEFAULTS.pageSize,
type: 'error,update', type: 'error,update',
status: 'unactioned',
}; };
const alerts = getNotes( alertsQuery ); // Filter out notes that may have been marked actioned or not delayed after the initial request
const filterNotes = note => 'unactioned' === note.status;
const alerts = getNotes( alertsQuery ).filter( filterNotes );
return { alerts }; const isLoading = isGetNotesRequesting( alertsQuery );
return {
alerts,
isLoading,
};
} ),
withDispatch( dispatch => {
const { updateNote } = dispatch( 'wc-api' );
return {
updateNote,
};
} ) } )
)( StoreAlerts ); )( StoreAlerts );

View File

@ -4,8 +4,10 @@
*/ */
import operations from './operations'; import operations from './operations';
import selectors from './selectors'; import selectors from './selectors';
import mutations from './mutations';
export default { export default {
operations, operations,
selectors, selectors,
mutations,
}; };

View File

@ -0,0 +1,10 @@
/** @format */
const updateNote = operations => ( noteId, noteFields ) => {
const resourceKey = 'note';
operations.update( [ resourceKey ], { [ resourceKey ]: { noteId, ...noteFields } } );
};
export default {
updateNote,
};

View File

@ -19,6 +19,10 @@ function read( resourceNames, fetch = apiFetch ) {
return [ ...readNotes( resourceNames, fetch ), ...readNoteQueries( resourceNames, fetch ) ]; return [ ...readNotes( resourceNames, fetch ), ...readNoteQueries( resourceNames, fetch ) ];
} }
function update( resourceNames, data, fetch = apiFetch ) {
return [ ...updateNote( resourceNames, data, fetch ) ];
}
function readNoteQueries( resourceNames, fetch ) { function readNoteQueries( resourceNames, fetch ) {
const filteredNames = resourceNames.filter( name => isResourcePrefix( name, 'note-query' ) ); const filteredNames = resourceNames.filter( name => isResourcePrefix( name, 'note-query' ) );
@ -71,6 +75,25 @@ function readNote( resourceName, fetch ) {
} ); } );
} }
function updateNote( resourceNames, data, fetch ) {
const resourceName = 'note';
if ( resourceNames.includes( resourceName ) ) {
const { noteId, ...noteFields } = data[ resourceName ];
const url = `${ NAMESPACE }/admin/notes/${ noteId }`;
return [
fetch( { path: url, method: 'PUT', data: noteFields } )
.then( note => {
return { [ resourceName + ':' + noteId ]: { data: note } };
} )
.catch( error => {
return { [ resourceName + ':' + noteId ]: { error } };
} ),
];
}
return [];
}
export default { export default {
read, read,
update,
}; };

View File

@ -16,6 +16,7 @@ function createWcApiSpec() {
mutations: { mutations: {
...settings.mutations, ...settings.mutations,
...user.mutations, ...user.mutations,
...notes.mutations,
}, },
selectors: { selectors: {
...items.selectors, ...items.selectors,
@ -42,6 +43,7 @@ function createWcApiSpec() {
return [ return [
...settings.operations.update( resourceNames, data ), ...settings.operations.update( resourceNames, data ),
...user.operations.update( resourceNames, data ), ...user.operations.update( resourceNames, data ),
...notes.operations.update( resourceNames, data ),
]; ];
}, },
}, },

View File

@ -63,6 +63,11 @@ class WC_Admin_REST_Admin_Notes_Controller extends WC_REST_CRUD_Controller {
'callback' => array( $this, 'get_item' ), 'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ),
), ),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_items_permissions_check' ),
),
'schema' => array( $this, 'get_public_item_schema' ), 'schema' => array( $this, 'get_public_item_schema' ),
) )
); );
@ -124,6 +129,12 @@ class WC_Admin_REST_Admin_Notes_Controller extends WC_REST_CRUD_Controller {
$args['type'] = $type; $args['type'] = $type;
} }
$status = isset( $request['status'] ) ? $request['status'] : '';
$status = sanitize_text_field( $status );
if ( ! empty( $status ) ) {
$args['status'] = $status;
}
$notes = WC_Admin_Notes::get_notes( 'edit', $args ); $notes = WC_Admin_Notes::get_notes( 'edit', $args );
$data = array(); $data = array();
@ -134,7 +145,7 @@ class WC_Admin_REST_Admin_Notes_Controller extends WC_REST_CRUD_Controller {
} }
$response = rest_ensure_response( $data ); $response = rest_ensure_response( $data );
$response->header( 'X-WP-Total', WC_Admin_Notes::get_notes_count() ); $response->header( 'X-WP-Total', WC_Admin_Notes::get_notes_count( $type, $status ) );
return $response; return $response;
} }
@ -167,6 +178,49 @@ class WC_Admin_REST_Admin_Notes_Controller extends WC_REST_CRUD_Controller {
return true; return true;
} }
/**
* Update a single note.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Request|WP_Error
*/
public function update_item( $request ) {
$note = WC_Admin_Notes::get_note( $request->get_param( 'id' ) );
if ( ! $note ) {
return new WP_Error(
'woocommerce_admin_notes_invalid_id',
__( 'Sorry, there is no resouce with that ID.', 'wc-admin' ),
array( 'status' => 404 )
);
}
// @todo Status is the only field that can be updated at the moment. We should also implement the "date reminder" setting.
$note_changed = false;
if ( ! is_null( $request->get_param( 'status' ) ) ) {
$note->set_status( $request->get_param( 'status' ) );
$note_changed = true;
}
if ( $note_changed ) {
$note->save();
}
return $this->get_item( $request );
}
/**
* Makes sure the current user has access to WRITE the settings APIs.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|bool
*/
public function update_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'settings', 'edit' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you cannot edit this resource.', 'wc-admin' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/** /**
* Prepare a path or query for serialization to the client. * Prepare a path or query for serialization to the client.
* *
@ -204,8 +258,9 @@ class WC_Admin_REST_Admin_Notes_Controller extends WC_REST_CRUD_Controller {
$data['title'] = stripslashes( $data['title'] ); $data['title'] = stripslashes( $data['title'] );
$data['content'] = stripslashes( $data['content'] ); $data['content'] = stripslashes( $data['content'] );
foreach ( (array) $data['actions'] as $key => $value ) { foreach ( (array) $data['actions'] as $key => $value ) {
$data['actions'][ $key ]->label = stripslashes( $data['actions'][ $key ]->label ); $data['actions'][ $key ]->label = stripslashes( $data['actions'][ $key ]->label );
$data['actions'][ $key ]->url = $this->prepare_query_for_response( $data['actions'][ $key ]->query ); $data['actions'][ $key ]->url = $this->prepare_query_for_response( $data['actions'][ $key ]->query );
$data['actions'][ $key ]->status = stripslashes( $data['actions'][ $key ]->status );
} }
$data = $this->filter_response_by_context( $data, $context ); $data = $this->filter_response_by_context( $data, $context );
@ -233,6 +288,29 @@ class WC_Admin_REST_Admin_Notes_Controller extends WC_REST_CRUD_Controller {
return apply_filters( 'woocommerce_rest_prepare_admin_note', $response, $data, $request ); return apply_filters( 'woocommerce_rest_prepare_admin_note', $response, $data, $request );
} }
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
$params = array();
$params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
$params['type'] = array(
'description' => __( 'Type of note.', 'wc-admin' ),
'type' => 'string',
'enum' => WC_Admin_Note::get_allowed_types(),
'validate_callback' => 'rest_validate_request_arg',
);
$params['status'] = array(
'description' => __( 'Status of note.', 'wc-admin' ),
'type' => 'string',
'enum' => WC_Admin_Note::get_allowed_statuses(),
'validate_callback' => 'rest_validate_request_arg',
);
return $params;
}
/** /**
* Get the note's schema, conforming to JSON Schema. * Get the note's schema, conforming to JSON Schema.
* *
@ -296,7 +374,6 @@ class WC_Admin_REST_Admin_Notes_Controller extends WC_REST_CRUD_Controller {
'description' => __( 'The status of the note (e.g. unactioned, actioned).', 'wc-admin' ), 'description' => __( 'The status of the note (e.g. unactioned, actioned).', 'wc-admin' ),
'type' => 'string', 'type' => 'string',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
'readonly' => true,
), ),
'source' => array( 'source' => array(
'description' => __( 'Source of the note.', 'wc-admin' ), 'description' => __( 'Source of the note.', 'wc-admin' ),
@ -320,7 +397,7 @@ class WC_Admin_REST_Admin_Notes_Controller extends WC_REST_CRUD_Controller {
'description' => __( 'Date after which the user should be reminded of the note, if any.', 'wc-admin' ), 'description' => __( 'Date after which the user should be reminded of the note, if any.', 'wc-admin' ),
'type' => 'string', 'type' => 'string',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
'readonly' => true, 'readonly' => true, // @todo Allow date_reminder to be updated.
), ),
'date_reminder_gmt' => array( 'date_reminder_gmt' => array(
'description' => __( 'Date after which the user should be reminded of the note, if any (GMT).', 'wc-admin' ), 'description' => __( 'Date after which the user should be reminded of the note, if any (GMT).', 'wc-admin' ),

View File

@ -16,7 +16,7 @@ class WC_Admin_Install {
* *
* @todo get this dynamically? * @todo get this dynamically?
*/ */
const VERSION_NUMBER = '0.6.0'; const VERSION_NUMBER = '0.8.0';
/** /**
* Plugin version option name. * Plugin version option name.
@ -76,6 +76,8 @@ class WC_Admin_Install {
self::create_tables(); self::create_tables();
self::update_wc_admin_version(); self::update_wc_admin_version();
WC_Admin_Notes_Historical_Data::add_note();
delete_transient( 'wc_admin_installing' ); delete_transient( 'wc_admin_installing' );
do_action( 'wc_admin_installed' ); do_action( 'wc_admin_installed' );
@ -174,6 +176,7 @@ class WC_Admin_Install {
name varchar(255) NOT NULL, name varchar(255) NOT NULL,
label varchar(255) NOT NULL, label varchar(255) NOT NULL,
query longtext NOT NULL, query longtext NOT NULL,
status varchar(255) NOT NULL,
PRIMARY KEY (action_id), PRIMARY KEY (action_id),
KEY note_id (note_id) KEY note_id (note_id)
) $collate; ) $collate;

View File

@ -365,6 +365,7 @@ class WC_Admin_Note extends WC_Data {
/** /**
* Set note data for potential re-localization. * Set note data for potential re-localization.
* *
* @todo Set a default empty array? https://github.com/woocommerce/wc-admin/pull/1763#pullrequestreview-212442921.
* @param object $content_data Note data. * @param object $content_data Note data.
*/ */
public function set_content_data( $content_data ) { public function set_content_data( $content_data ) {
@ -450,11 +451,13 @@ class WC_Admin_Note extends WC_Data {
* @param string $name Label name (not presented to user). * @param string $name Label name (not presented to user).
* @param string $label Note label (e.g. presented as button label). * @param string $label Note label (e.g. presented as button label).
* @param string $query Note query (for redirect). * @param string $query Note query (for redirect).
* @param string $status The status to set for the action should on click.
*/ */
public function add_action( $name, $label, $query ) { public function add_action( $name, $label, $query, $status = '' ) {
$name = wc_clean( $name ); $name = wc_clean( $name );
$label = wc_clean( $label ); $label = wc_clean( $label );
$query = wc_clean( $query ); $query = wc_clean( $query );
$status = wc_clean( $status );
if ( empty( $name ) ) { if ( empty( $name ) ) {
$this->error( 'admin_note_invalid_data', __( 'The admin note action name prop cannot be empty.', 'wc-admin' ) ); $this->error( 'admin_note_invalid_data', __( 'The admin note action name prop cannot be empty.', 'wc-admin' ) );
@ -469,9 +472,10 @@ class WC_Admin_Note extends WC_Data {
} }
$action = array( $action = array(
'name' => $name, 'name' => $name,
'label' => $label, 'label' => $label,
'query' => $query, 'query' => $query,
'status' => $status,
); );
$note_actions = $this->get_prop( 'actions', 'edit' ); $note_actions = $this->get_prop( 'actions', 'edit' );

View File

@ -0,0 +1,50 @@
<?php
/**
* WooCommerce Admin: Historical Analytics Data Note.
*
* Adds a notes to store alerts area concerning the historial analytics data tool.
*
* @package WooCommerce Admin
*/
defined( 'ABSPATH' ) || exit;
/**
* WC_Admin_Notes_Historical_Data.
*/
class WC_Admin_Notes_Historical_Data {
const NOTE_NAME = 'wc-admin-historical-data';
/**
* Creates a note for regenerating historical data.
*/
public static function add_note() {
$data_store = WC_Data_Store::load( 'admin-note' );
// First, see if we've already created this kind of note so we don't do it again.
$note_ids = $data_store->get_notes_with_name( self::NOTE_NAME );
if ( ! empty( $note_ids ) ) {
return;
}
$note = new WC_Admin_Note();
$note->set_title( __( 'WooCommerce Admin: Historical Analytics Data', 'wc-admin' ) );
$note->set_content( __( 'To view your historical analytics data, you must process your existing orders and customers.', 'wc-admin' ) );
$note->set_type( WC_Admin_Note::E_WC_ADMIN_NOTE_UPDATE );
$note->set_icon( 'info' );
$note->set_name( self::NOTE_NAME );
$note->set_content_data( (object) array() );
$note->set_source( 'woocommerce-admin' );
// @todo Add remind me later option. See https://github.com/woocommerce/wc-admin/issues/1756.
$note->add_action(
'get-started',
__( 'Get Started', 'wc-admin' ),
'?page=wc-admin#/analytics/settings',
'actioned'
);
$note->save();
}
}
new WC_Admin_Notes_Historical_Data();

View File

@ -94,7 +94,7 @@ class WC_Admin_Notes_New_Sales_Record {
$note->set_type( WC_Admin_Note::E_WC_ADMIN_NOTE_INFORMATIONAL ); $note->set_type( WC_Admin_Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_icon( 'trophy' ); $note->set_icon( 'trophy' );
$note->set_name( self::NOTE_NAME ); $note->set_name( self::NOTE_NAME );
$note->set_source( 'wc-admin' ); $note->set_source( 'woocommerce-admin' );
$note->add_action( 'view-report', __( 'View report', 'wc-admin' ), '?page=wc-admin#/analytics' ); $note->add_action( 'view-report', __( 'View report', 'wc-admin' ), '?page=wc-admin#/analytics' );
$note->save(); $note->save();
} }

View File

@ -42,7 +42,7 @@ class WC_Admin_Notes_Settings_Notes {
$note->set_type( WC_Admin_Note::E_WC_ADMIN_NOTE_INFORMATIONAL ); $note->set_type( WC_Admin_Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_icon( 'info' ); $note->set_icon( 'info' );
$note->set_name( $name ); $note->set_name( $name );
$note->set_source( 'wc-admin' ); $note->set_source( 'woocommerce-admin' );
$note->add_action( $note->add_action(
'open-customizer', 'open-customizer',
__( 'Open Customizer', 'wc-admin' ), __( 'Open Customizer', 'wc-admin' ),

View File

@ -185,7 +185,7 @@ class WC_Admin_Notes_Woo_Subscriptions_Notes {
$note->set_type( WC_Admin_Note::E_WC_ADMIN_NOTE_INFORMATIONAL ); $note->set_type( WC_Admin_Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_icon( 'info' ); $note->set_icon( 'info' );
$note->set_name( self::CONNECTION_NOTE_NAME ); $note->set_name( self::CONNECTION_NOTE_NAME );
$note->set_source( 'wc-admin' ); $note->set_source( 'woocommerce-admin' );
$note->add_action( $note->add_action(
'connect', 'connect',
__( 'Connect', 'wc-admin' ), __( 'Connect', 'wc-admin' ),
@ -334,7 +334,7 @@ class WC_Admin_Notes_Woo_Subscriptions_Notes {
$note->set_type( WC_Admin_Note::E_WC_ADMIN_NOTE_WARNING ); $note->set_type( WC_Admin_Note::E_WC_ADMIN_NOTE_WARNING );
$note->set_icon( 'notice' ); $note->set_icon( 'notice' );
$note->set_name( self::SUBSCRIPTION_NOTE_NAME ); $note->set_name( self::SUBSCRIPTION_NOTE_NAME );
$note->set_source( 'wc-admin' ); $note->set_source( 'woocommerce-admin' );
$note->clear_actions(); $note->clear_actions();
$note->add_action( $note->add_action(
'enable-autorenew', 'enable-autorenew',
@ -398,7 +398,7 @@ class WC_Admin_Notes_Woo_Subscriptions_Notes {
$note->set_type( WC_Admin_Note::E_WC_ADMIN_NOTE_WARNING ); $note->set_type( WC_Admin_Note::E_WC_ADMIN_NOTE_WARNING );
$note->set_icon( 'notice' ); $note->set_icon( 'notice' );
$note->set_name( self::SUBSCRIPTION_NOTE_NAME ); $note->set_name( self::SUBSCRIPTION_NOTE_NAME );
$note->set_source( 'wc-admin' ); $note->set_source( 'woocommerce-admin' );
$note->clear_actions(); $note->clear_actions();
$note->add_action( $note->add_action(
'renew-subscription', 'renew-subscription',

View File

@ -64,11 +64,12 @@ class WC_Admin_Notes {
* Get the total number of notes * Get the total number of notes
* *
* @param string $type Comma separated list of note types. * @param string $type Comma separated list of note types.
* @param string $status Comma separated list of statuses.
* @return int * @return int
*/ */
public static function get_notes_count( $type = '' ) { public static function get_notes_count( $type = '', $status = '' ) {
$data_store = WC_Data_Store::load( 'admin-note' ); $data_store = WC_Data_Store::load( 'admin-note' );
return $data_store->get_notes_count( $type ); return $data_store->get_notes_count( $type, $status );
} }
/** /**

View File

@ -148,7 +148,6 @@ class WC_Admin_Reports_Sync {
); );
} }
/** /**
* Schedule an action to process a single Order. * Schedule an action to process a single Order.
* *

View File

@ -197,13 +197,13 @@ class WC_Admin_Notes_Data_Store extends WC_Data_Store_WP implements WC_Object_Da
$actions = $wpdb->get_results( $actions = $wpdb->get_results(
$wpdb->prepare( $wpdb->prepare(
"SELECT name, label, query FROM {$wpdb->prefix}wc_admin_note_actions WHERE note_id = %d", "SELECT name, label, query, status FROM {$wpdb->prefix}wc_admin_note_actions WHERE note_id = %d",
$note->get_id() $note->get_id()
) )
); );
if ( $actions ) { if ( $actions ) {
foreach ( $actions as $action ) { foreach ( $actions as $action ) {
$note->add_action( $action->name, $action->label, $action->query ); $note->add_action( $action->name, $action->label, $action->query, $action->status );
} }
} }
} }
@ -232,6 +232,7 @@ class WC_Admin_Notes_Data_Store extends WC_Data_Store_WP implements WC_Object_Da
'name' => $action->name, 'name' => $action->name,
'label' => $action->label, 'label' => $action->label,
'query' => $action->query, 'query' => $action->query,
'status' => $action->status,
) )
); );
} }
@ -258,6 +259,49 @@ class WC_Admin_Notes_Data_Store extends WC_Data_Store_WP implements WC_Object_Da
$offset = $per_page * ( $page - 1 ); $offset = $per_page * ( $page - 1 );
$where_clauses = $this->get_notes_where_clauses( $args );
$query = $wpdb->prepare(
"SELECT note_id, title, content FROM {$wpdb->prefix}wc_admin_notes WHERE 1=1{$where_clauses} ORDER BY note_id DESC LIMIT %d, %d",
$offset,
$per_page
); // WPCS: unprepared SQL ok.
return $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
}
/**
* Return a count of notes.
*
* @param string $type Comma separated list of note types.
* @param string $status Comma separated list of statuses.
* @return array An array of objects containing a note id.
*/
public function get_notes_count( $type = '', $status = '' ) {
global $wpdb;
$where_clauses = $this->get_notes_where_clauses(
array(
'type' => $type,
'status' => $status,
)
);
if ( ! empty( $where_clauses ) ) {
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
return $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}wc_admin_notes WHERE 1=1{$where_clauses}" );
}
return $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}wc_admin_notes" );
}
/**
* Return where clauses for getting notes by status and type. For use in both the count and listing queries.
*
* @param array $args Array of args to pass.
* @return string Where clauses for the query.
*/
public function get_notes_where_clauses( $args = array() ) {
$allowed_types = WC_Admin_Note::get_allowed_types(); $allowed_types = WC_Admin_Note::get_allowed_types();
$where_type_array = array(); $where_type_array = array();
if ( isset( $args['type'] ) ) { if ( isset( $args['type'] ) ) {
@ -269,54 +313,32 @@ class WC_Admin_Notes_Data_Store extends WC_Data_Store_WP implements WC_Object_Da
} }
} }
} }
$escaped_where_types = implode( ',', $where_type_array );
if ( empty( $escaped_where_types ) ) { $allowed_statuses = WC_Admin_Note::get_allowed_statuses();
$query = $wpdb->prepare( $where_status_array = array();
"SELECT note_id, title, content FROM {$wpdb->prefix}wc_admin_notes ORDER BY note_id DESC LIMIT %d, %d", if ( isset( $args['status'] ) ) {
$offset, $args_statuses = explode( ',', $args['status'] );
$per_page foreach ( (array) $args_statuses as $args_status ) {
); $args_status = trim( $args_status );
} else { if ( in_array( $args_status, $allowed_statuses, true ) ) {
$query = $wpdb->prepare( $where_status_array[] = "'" . esc_sql( $args_status ) . "'";
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
"SELECT note_id, title, content FROM {$wpdb->prefix}wc_admin_notes WHERE type IN ($escaped_where_types) ORDER BY note_id DESC LIMIT %d, %d",
$offset,
$per_page
);
}
return $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
}
/**
* Return a count of notes.
*
* @param string $type Comma separated list of note types.
* @return array An array of objects containing a note id.
*/
public function get_notes_count( $type = '' ) {
global $wpdb;
$allowed_types = WC_Admin_Note::get_allowed_types();
$where_type_array = array();
if ( ! empty( $type ) ) {
$args_types = explode( ',', $type );
foreach ( (array) $args_types as $args_type ) {
$args_type = trim( $args_type );
if ( in_array( $args_type, $allowed_types, true ) ) {
$where_type_array[] = "'" . esc_sql( $args_type ) . "'";
} }
} }
} }
$escaped_where_types = implode( ',', $where_type_array );
$escaped_where_types = implode( ',', $where_type_array );
$escaped_status_types = implode( ',', $where_status_array );
$where_clauses = '';
if ( ! empty( $escaped_where_types ) ) { if ( ! empty( $escaped_where_types ) ) {
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $where_clauses .= " AND type IN ($escaped_where_types)";
return $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}wc_admin_notes WHERE type IN ($escaped_where_types)" );
} }
return $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}wc_admin_notes" ); if ( ! empty( $escaped_status_types ) ) {
$where_clauses .= " AND status IN ($escaped_status_types)";
}
return $where_clauses;
} }
/** /**

View File

@ -191,7 +191,7 @@ function wc_admin_print_script_settings() {
'weekdaysShort' => array_values( $wp_locale->weekday_abbrev ), 'weekdaysShort' => array_values( $wp_locale->weekday_abbrev ),
), ),
'currentUserData' => $current_user_data, 'currentUserData' => $current_user_data,
'alertCount' => WC_Admin_Notes::get_notes_count( 'error,update' ), 'alertCount' => WC_Admin_Notes::get_notes_count( 'error,update', 'unactioned' ),
); );
$settings = wc_admin_add_custom_settings( $settings ); $settings = wc_admin_add_custom_settings( $settings );

View File

@ -106,6 +106,45 @@ class WC_Tests_API_Admin_Notes extends WC_REST_Unit_Test_Case {
$this->assertEquals( 401, $response->get_status() ); $this->assertEquals( 401, $response->get_status() );
} }
/**
* Test updating a single note.
*/
public function test_update_note() {
wp_set_current_user( $this->user );
$response = $this->server->dispatch( new WP_REST_Request( 'GET', $this->endpoint . '/1' ) );
$note = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 'unactioned', $note['status'] );
$request = new WP_REST_Request( 'PUT', $this->endpoint . '/1' );
$request->set_body_params(
array(
'status' => 'actioned',
)
);
$response = $this->server->dispatch( $request );
$note = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 'actioned', $note['status'] );
}
/**
* Test updating a single note without permission. It should fail.
*/
public function test_update_note_without_permission() {
$request = new WP_REST_Request( 'PUT', $this->endpoint . '/1' );
$request->set_body_params(
array(
'status' => 'actioned',
)
);
$response = $this->server->dispatch( $request );
$this->assertEquals( 401, $response->get_status() );
}
/** /**
* Test getting lots of notes. * Test getting lots of notes.
* *
@ -139,6 +178,31 @@ class WC_Tests_API_Admin_Notes extends WC_REST_Unit_Test_Case {
$this->assertEquals( $notes[0]['title'], 'PHPUNIT_TEST_NOTE_2_TITLE' ); $this->assertEquals( $notes[0]['title'], 'PHPUNIT_TEST_NOTE_2_TITLE' );
} }
/**
* Test getting notes of a certain status.
*/
public function test_get_actioned_notes() {
wp_set_current_user( $this->user );
$request = new WP_REST_Request( 'GET', $this->endpoint );
$request->set_query_params( array( 'status' => 'actioned' ) );
$response = $this->server->dispatch( $request );
$notes = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 1, count( $notes ) );
$this->assertEquals( $notes[0]['title'], 'PHPUNIT_TEST_NOTE_2_TITLE' );
$request = new WP_REST_Request( 'GET', $this->endpoint );
$request->set_query_params( array( 'status' => 'invalid' ) );
$response = $this->server->dispatch( $request );
$notes = $response->get_data();
// get_notes returns all results since 'status' is not one of actioned or unactioned.
$this->assertEquals( 2, count( $notes ) );
}
/** /**
* Test getting lots of notes without permission. It should fail. * Test getting lots of notes without permission. It should fail.
* *

View File

@ -55,6 +55,7 @@ class WC_Helper_Admin_Notes {
$note_2->set_icon( 'info' ); $note_2->set_icon( 'info' );
$note_2->set_name( 'PHPUNIT_TEST_NOTE_NAME' ); $note_2->set_name( 'PHPUNIT_TEST_NOTE_NAME' );
$note_2->set_source( 'PHPUNIT_TEST' ); $note_2->set_source( 'PHPUNIT_TEST' );
$note_2->set_status( WC_Admin_Note::E_WC_ADMIN_NOTE_ACTIONED );
// This note has no actions. // This note has no actions.
$note_2->save(); $note_2->save();

View File

@ -153,6 +153,7 @@ function wc_admin_plugins_loaded() {
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-notes-new-sales-record.php'; require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-notes-new-sales-record.php';
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-notes-settings-notes.php'; require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-notes-settings-notes.php';
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-notes-woo-subscriptions-notes.php'; require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-notes-woo-subscriptions-notes.php';
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-notes-historical-data.php';
// Verify we have a proper build. // Verify we have a proper build.
if ( ! wc_admin_build_file_exists() ) { if ( ! wc_admin_build_file_exists() ) {