From eeadce1235cec3c51fa37fad4be28365a9c51294 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Mon, 24 May 2021 14:50:44 -0300 Subject: [PATCH 1/5] Added cron manual trigger This commit adds a manual trigger for the selected cron # Conflicts: # src/tools/commands/index.js # src/tools/data/actions.js --- api/api.php | 1 + api/tools/trigger-cron-job.php | 95 ++++++++++++++ src/index.scss | 7 + src/tools/{commands.js => commands/index.js} | 7 + src/tools/commands/trigger-cron.js | 53 ++++++++ src/tools/data/action-types.js | 2 + src/tools/data/actions.js | 130 +++++++++++-------- src/tools/data/index.js | 2 + src/tools/data/reducer.js | 14 ++ src/tools/data/resolvers.js | 24 ++++ src/tools/data/selectors.js | 8 ++ src/tools/index.js | 16 ++- 12 files changed, 299 insertions(+), 60 deletions(-) create mode 100644 api/tools/trigger-cron-job.php rename src/tools/{commands.js => commands/index.js} (86%) create mode 100644 src/tools/commands/trigger-cron.js create mode 100644 src/tools/data/resolvers.js diff --git a/api/api.php b/api/api.php index 02544cb790a..43ffa5f5754 100644 --- a/api/api.php +++ b/api/api.php @@ -29,6 +29,7 @@ function register_woocommerce_admin_test_helper_rest_route( $route, $callback, $ require( 'admin-notes/delete-all-notes.php' ); require( 'admin-notes/add-note.php' ); require( 'tools/trigger-wca-install.php' ); +require( 'tools/trigger-cron-job.php' ); require( 'tools/run-wc-admin-daily.php' ); require( 'options/rest-api.php' ); require( 'tools/delete-all-products.php'); diff --git a/api/tools/trigger-cron-job.php b/api/tools/trigger-cron-job.php new file mode 100644 index 00000000000..a1d21b2a15c --- /dev/null +++ b/api/tools/trigger-cron-job.php @@ -0,0 +1,95 @@ + 'POST', + 'args' => array( + 'hook' => array( + 'description' => 'Name of the cron that will be triggered.', + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + ), + 'signature' => array( + 'description' => 'Signature of the cron to trigger.', + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + ) +); + +function tools_get_cron_list() { + $crons = _get_cron_array(); + $events = array(); + + if ( empty( $crons ) ) { + return array(); + } + + foreach ( $crons as $cron ) { + foreach ( $cron as $hook => $data ) { + foreach ( $data as $signature => $element ) { + $events[ $hook ] = (object) array( + 'hook' => $hook, + 'signature' => $signature, + ); + } + } + } + return new WP_REST_Response( $events, 200 ); +} + +function trigger_selected_cron( $request ) { + $hook = $request->get_param( 'hook' ); + $signature = $request->get_param( 'signature' ); + + if ( ! isset( $hook ) || ! isset( $signature ) ) { + return; + } + + $crons = _get_cron_array(); + foreach ( $crons as $cron ) { + if ( isset( $cron[ $hook ][ $signature ] ) ) { + $args = $cron[ $hook ][ $signature ]['args']; + delete_transient( 'doing_cron' ); + $scheduled = schedule_event( $hook, $args ); + + if ( false === $scheduled ) { + return $scheduled; + } + + add_filter( 'cron_request', function( array $cron_request ) { + $cron_request['url'] = add_query_arg( 'run-cron', 1, $cron_request['url'] ); + return $cron_request; + } ); + + spawn_cron(); + sleep( 1 ); + return true; + } + } + return false; +} + +function schedule_event( $hook, $args = array() ) { + $event = (object) array( + 'hook' => $hook, + 'timestamp' => 1, + 'schedule' => false, + 'args' => $args, + ); + $crons = (array) _get_cron_array(); + $key = md5( serialize( $event->args ) ); + + $crons[ $event->timestamp ][ $event->hook ][ $key ] = array( + 'schedule' => $event->schedule, + 'args' => $event->args, + ); + uksort( $crons, 'strnatcasecmp' ); + return _set_cron_array( $crons ); +} diff --git a/src/index.scss b/src/index.scss index d91655c9eeb..9b75936e7e9 100644 --- a/src/index.scss +++ b/src/index.scss @@ -60,6 +60,13 @@ &.command { white-space: nowrap; } + .trigger-cron-job { + width: 40%; + padding-top: 4px; + .components-base-control__field { + margin-bottom: 0; + } + } } } .components-notice { diff --git a/src/tools/commands.js b/src/tools/commands/index.js similarity index 86% rename from src/tools/commands.js rename to src/tools/commands/index.js index 221b74abcc8..0e43792989c 100644 --- a/src/tools/commands.js +++ b/src/tools/commands/index.js @@ -1,3 +1,5 @@ +import { TriggerCronJob, TRIGGER_CRON_ACTION_NAME } from './trigger-cron'; + export default [ { command: 'Trigger WCA Install', @@ -39,4 +41,9 @@ export default [ description: 'Delete all products', action: 'deleteAllProducts', }, + { + command: 'Run a cron job', + description: , + action: TRIGGER_CRON_ACTION_NAME, + }, ]; diff --git a/src/tools/commands/trigger-cron.js b/src/tools/commands/trigger-cron.js new file mode 100644 index 00000000000..34d871a5014 --- /dev/null +++ b/src/tools/commands/trigger-cron.js @@ -0,0 +1,53 @@ +/** + * External dependencies. + */ + import { SelectControl } from '@wordpress/components'; + import { withDispatch, withSelect } from '@wordpress/data'; + import { compose } from '@wordpress/compose'; + + /** + * Internal dependencies + */ +import { STORE_KEY } from '../data/constants'; + +export const TRIGGER_CRON_ACTION_NAME = 'runSelectedCronJob'; + + const TriggerCron = ( { cronList, updateCommandParams } ) => { + function onCronChange( selectedValue ) { + const { hook, signature } = cronList[ selectedValue ]; + updateCommandParams( TRIGGER_CRON_ACTION_NAME, { hook, signature } ); + } + + function getOptions( cronList ) { + return Object.keys( cronList ).map( ( name ) => { + return { label: name, value: name } + } ); + } + + return ( +
+ { ! cronList + ?

Loading ...

+ : + } +
+ ); +}; + +export const TriggerCronJob = compose( + withSelect( ( select ) => { + const { getCronJobs } = select( STORE_KEY ); + return { + cronList: getCronJobs() + }; + } ), + withDispatch( ( dispatch ) => { + const { updateCommandParams } = dispatch( STORE_KEY ); + return { updateCommandParams }; + } ) +)( TriggerCron ); \ No newline at end of file diff --git a/src/tools/data/action-types.js b/src/tools/data/action-types.js index fa8cd96caf5..b722f161f37 100644 --- a/src/tools/data/action-types.js +++ b/src/tools/data/action-types.js @@ -4,6 +4,8 @@ const TYPES = { ADD_MESSAGE: 'ADD_MESSAGE', UPDATE_MESSAGE: 'UPDATE_MESSAGE', REMOVE_MESSAGE: 'REMOVE_MESSAGE', + ADD_COMMAND_PARAMS: 'ADD_COMMAND_PARAMS', + SET_CRON_JOBS: 'SET_CRON_JOBS', }; export default TYPES; diff --git a/src/tools/data/actions.js b/src/tools/data/actions.js index 46c1e4a0faf..2cad7873301 100644 --- a/src/tools/data/actions.js +++ b/src/tools/data/actions.js @@ -9,21 +9,21 @@ import { apiFetch } from '@wordpress/data-controls'; import TYPES from './action-types'; import { API_NAMESPACE } from './constants'; -export function addCurrentlyRunning( command ) { +export function addCurrentlyRunning(command) { return { type: TYPES.ADD_CURRENTLY_RUNNING, command, }; } -export function removeCurrentlyRunning( command ) { +export function removeCurrentlyRunning(command) { return { type: TYPES.REMOVE_CURRENTLY_RUNNING, command, }; } -export function addMessage( source, message ) { +export function addMessage(source, message) { return { type: TYPES.ADD_MESSAGE, source, @@ -31,7 +31,7 @@ export function addMessage( source, message ) { }; } -export function updateMessage( source, message, status ) { +export function updateMessage(source, message, status) { return { type: TYPES.ADD_MESSAGE, source, @@ -40,69 +40,84 @@ export function updateMessage( source, message, status ) { }; } -export function removeMessage( source ) { +export function removeMessage(source) { return { type: TYPES.REMOVE_MESSAGE, source, }; } -function* runCommand( commandName, func ) { +export function updateCommandParams(source, params) { + return { + type: TYPES.ADD_COMMAND_PARAMS, + source, + params, + }; +} + +export function setCronJobs(cronJobs) { + return { + type: TYPES.SET_CRON_JOBS, + cronJobs, + }; +} + +function* runCommand(commandName, func) { try { - yield addCurrentlyRunning( commandName ); - yield addMessage( commandName, 'Executing...' ); + yield addCurrentlyRunning(commandName); + yield addMessage(commandName, 'Executing...'); yield func(); - yield removeCurrentlyRunning( commandName ); - yield updateMessage( commandName, 'Successful!' ); - } catch ( e ) { - yield updateMessage( commandName, e.message, 'error' ); - yield removeCurrentlyRunning( commandName ); + yield removeCurrentlyRunning(commandName); + yield updateMessage(commandName, 'Successful!'); + } catch (e) { + yield updateMessage(commandName, e.message, 'error'); + yield removeCurrentlyRunning(commandName); } } export function* triggerWcaInstall() { - yield runCommand( 'Trigger WCA Install', function* () { - yield apiFetch( { + yield runCommand('Trigger WCA Install', function* () { + yield apiFetch({ path: API_NAMESPACE + '/tools/trigger-wca-install/v1', method: 'POST', - } ); - } ); + }); + }); } export function* resetOnboardingWizard() { - yield runCommand( 'Reset Onboarding Wizard', function* () { + yield runCommand('Reset Onboarding Wizard', function* () { const optionsToDelete = [ 'woocommerce_task_list_tracked_completed_tasks', 'woocommerce_onboarding_profile', '_transient_wc_onboarding_themes', ]; - yield apiFetch( { + yield apiFetch({ method: 'DELETE', - path: `${ API_NAMESPACE }/options/${ optionsToDelete.join( ',' ) }`, - } ); - } ); + path: `${API_NAMESPACE}/options/${optionsToDelete.join(',')}`, + }); + }); } export function* resetJetpackConnection() { - yield runCommand( 'Reset Jetpack Connection', function* () { - yield apiFetch( { + yield runCommand('Reset Jetpack Connection', function* () { + yield apiFetch({ method: 'DELETE', - path: `${ API_NAMESPACE }/options/jetpack_options`, - } ); - } ); + path: `${API_NAMESPACE}/options/jetpack_options`, + }); + }); } export function* enableTrackingDebug() { - yield runCommand( 'Enable WC Admin Tracking Debug Mode', function* () { - window.localStorage.setItem( 'debug', 'wc-admin:*' ); - } ); + yield runCommand('Enable WC Admin Tracking Debug Mode', function* () { + window.localStorage.setItem('debug', 'wc-admin:*'); + }); } export function* updateStoreAge() { - yield runCommand( 'Update Installation timestamp', function* () { + yield runCommand('Update Installation timestamp', function* () { const today = new Date(); - const dd = String( today.getDate() ).padStart( 2, '0' ); - const mm = String( today.getMonth() + 1 ).padStart( 2, '0' ); //January is 0! + const dd = String(today.getDate()).padStart(2, '0'); + const mm = String(today.getMonth() + 1).padStart(2, '0'); //January is 0! const yyyy = today.getFullYear(); // eslint-disable-next-line no-alert @@ -111,43 +126,52 @@ export function* updateStoreAge() { yyyy + '/' + mm + '/' + dd ); - if ( numberOfDays !== null ) { - const dates = numberOfDays.split( '/' ); + if (numberOfDays !== null) { + const dates = numberOfDays.split('/'); const newTimestamp = Math.round( - new Date( dates[ 0 ], dates[ 1 ] - 1, dates[ 2 ] ).getTime() / - 1000 + new Date(dates[0], dates[1] - 1, dates[2]).getTime() / 1000 ); const payload = { - woocommerce_admin_install_timestamp: JSON.parse( newTimestamp ), + woocommerce_admin_install_timestamp: JSON.parse(newTimestamp), }; - yield apiFetch( { + yield apiFetch({ method: 'POST', path: '/wc-admin/options', headers: { 'content-type': 'application/json' }, - body: JSON.stringify( payload ), - } ); + body: JSON.stringify(payload), + }); } - } ); + }); } export function* runWcAdminDailyJob() { - yield runCommand( 'Run wc_admin_daily job', function* () { - yield apiFetch( { + yield runCommand('Run wc_admin_daily job', function* () { + yield apiFetch({ path: API_NAMESPACE + '/tools/run-wc-admin-daily/v1', method: 'POST', - } ); - } ); + }); + }); } export function* deleteAllProducts() { - if ( ! confirm( 'Are you sure you want to delete all of the products?' ) ) { + if (!confirm('Are you sure you want to delete all of the products?')) { return; } - - yield runCommand( 'Delete all products', function* () { - yield apiFetch( { - path: `${ API_NAMESPACE }/tools/delete-all-products/v1`, + + yield runCommand('Delete all products', function* () { + yield apiFetch({ + path: `${API_NAMESPACE}/tools/delete-all-products/v1`, method: 'POST', - } ); - } ); + }); + }); +} + +export function* runSelectedCronJob(params) { + yield runCommand('Run selected cron job', function* () { + yield apiFetch({ + path: API_NAMESPACE + '/tools/run-wc-admin-daily/v1', + method: 'POST', + data: params, + }); + }); } diff --git a/src/tools/data/index.js b/src/tools/data/index.js index 968fffd7b4c..e476479ea88 100644 --- a/src/tools/data/index.js +++ b/src/tools/data/index.js @@ -8,6 +8,7 @@ import { controls } from '@wordpress/data-controls'; * Internal dependencies */ import * as actions from './actions'; +import * as resolvers from './resolvers'; import * as selectors from './selectors'; import reducer from './reducer'; import { STORE_KEY } from './constants'; @@ -15,6 +16,7 @@ import { STORE_KEY } from './constants'; export default registerStore( STORE_KEY, { actions, selectors, + resolvers, controls, reducer, } ); diff --git a/src/tools/data/reducer.js b/src/tools/data/reducer.js index 56f9a1a052e..dd534b81932 100644 --- a/src/tools/data/reducer.js +++ b/src/tools/data/reducer.js @@ -6,7 +6,9 @@ import TYPES from './action-types'; const DEFAULT_STATE = { currentlyRunning: {}, errorMessages: [], + cronJobs: false, messages: {}, + params: [], status: '', }; @@ -54,6 +56,18 @@ const reducer = ( state = DEFAULT_STATE, action ) => { [ action.command ]: false, }, }; + case TYPES.SET_CRON_JOBS: + return { + ...state, + cronJobs: action.cronJobs, + }; + case TYPES.ADD_COMMAND_PARAMS: + return { + ...state, + params: { + [ action.source ]: action.params, + }, + }; default: return state; } diff --git a/src/tools/data/resolvers.js b/src/tools/data/resolvers.js new file mode 100644 index 00000000000..6b78a030292 --- /dev/null +++ b/src/tools/data/resolvers.js @@ -0,0 +1,24 @@ +/** + * External dependencies + */ +import { apiFetch } from '@wordpress/data-controls'; + +/** + * Internal dependencies + */ +import { API_NAMESPACE } from './constants'; +import { setCronJobs } from './actions'; + +export function* getCronJobs() { + const path = `${ API_NAMESPACE }/tools/get-cron-list/v1`; + + try { + const response = yield apiFetch( { + path, + method: 'POST', + } ); + yield setCronJobs( response ); + } catch ( error ) { + throw new Error( error ); + } +} diff --git a/src/tools/data/selectors.js b/src/tools/data/selectors.js index 3cda1c6747a..aa4bdfeaac1 100644 --- a/src/tools/data/selectors.js +++ b/src/tools/data/selectors.js @@ -9,3 +9,11 @@ export function getMessages( state ) { export function getStatus( state ) { return state.status; } + +export function getCommandParams( state ) { + return state.params; +} + +export function getCronJobs( state ) { + return state.cronJobs; +} diff --git a/src/tools/index.js b/src/tools/index.js index e3a6d2da95b..5ba21f88b0c 100644 --- a/src/tools/index.js +++ b/src/tools/index.js @@ -12,7 +12,7 @@ import { default as commands } from './commands'; import { STORE_KEY } from './data/constants'; import './data'; -function Tools( { actions, currentlyRunningCommands, messages } ) { +function Tools( { actions, currentlyRunningCommands, messages, comandParams } ) { actions = actions(); return (
@@ -38,17 +38,18 @@ function Tools( { actions, currentlyRunningCommands, messages } ) { - { commands.map( ( command, index ) => { + { commands.map( ( { action, command, description }, index ) => { + const params = comandParams[ action ] ?? false; return ( - { command.command } - { command.description } + { command } + { description }
); }; - -export const TriggerCronJob = compose( - withSelect((select) => { - const { getCronJobs } = select(STORE_KEY); - return { - cronList: getCronJobs(), - }; - }), - withDispatch((dispatch) => { - const { updateCommandParams } = dispatch(STORE_KEY); - return { updateCommandParams }; - }) -)(TriggerCron);