From 6ded9053b60db55aa03fac5b82439f1c0270361d Mon Sep 17 00:00:00 2001 From: Paul Sealock Date: Tue, 7 Jun 2022 15:18:37 +1200 Subject: [PATCH] Move admin tester folders to root --- .../{admin => }/api/admin-notes/add-note.php | 0 .../api/admin-notes/delete-all-notes.php | 0 .../{admin => }/api/api.php | 0 .../api/api/admin-notes/add-note.php | 67 +++++ .../api/api/admin-notes/delete-all-notes.php | 18 ++ .../woocommerce-beta-tester/api/api/api.php | 41 +++ .../{admin => api}/api/features/features.php | 0 .../{admin => api}/api/options/rest-api.php | 0 .../api/rest-api-filters/hook.php | 0 .../api/rest-api-filters/rest-api-filters.php | 0 .../api/tools/delete-all-products.php | 0 .../api/tools/disable-wc-email.php | 0 .../api/tools/run-wc-admin-daily.php | 0 .../api/tools/trigger-cron-job.php | 0 .../api/tools/trigger-update-callbacks.php | 0 .../api/tools/trigger-wca-install.php | 0 .../api/tracks/tracks-debug-log.php | 0 .../api/features/features.php | 59 ++++ .../api/options/rest-api.php | 79 ++++++ .../api/rest-api-filters/hook.php | 52 ++++ .../api/rest-api-filters/rest-api-filters.php | 109 ++++++++ .../api/tools/delete-all-products.php | 16 ++ .../api/tools/disable-wc-email.php | 46 ++++ .../api/tools/run-wc-admin-daily.php | 11 + .../api/tools/trigger-cron-job.php | 98 +++++++ .../api/tools/trigger-update-callbacks.php | 47 ++++ .../api/tools/trigger-wca-install.php | 11 + .../api/tracks/tracks-debug-log.php | 53 ++++ .../{admin => }/images/admin-notes/banner.jpg | Bin .../images/admin-notes/thumbnail.jpg | Bin .../admin-notes/woocommerce-logo-vector.png | Bin .../images/images/admin-notes/banner.jpg | Bin 0 -> 10921 bytes .../images/images/admin-notes/thumbnail.jpg | Bin 0 -> 21461 bytes .../admin-notes/woocommerce-logo-vector.png | Bin 0 -> 5970 bytes .../{admin => }/src/admin-notes/add-note.js | 0 .../src/admin-notes/admin-notes.js | 0 .../src/admin-notes/delete-all-notes.js | 0 .../{admin => }/src/admin-notes/index.js | 0 .../{admin => }/src/app/app.js | 0 .../{admin => }/src/app/index.js | 0 .../src/experiments/NewExperimentForm.js | 0 .../src/experiments/data/action-types.js | 0 .../src/experiments/data/actions.js | 0 .../src/experiments/data/constants.js | 0 .../{admin => }/src/experiments/data/index.js | 0 .../src/experiments/data/reducer.js | 0 .../src/experiments/data/resolvers.js | 0 .../src/experiments/data/selectors.js | 0 .../{admin => }/src/experiments/index.js | 0 .../src/features/data/action-types.js | 0 .../{admin => }/src/features/data/actions.js | 0 .../src/features/data/constants.js | 0 .../{admin => }/src/features/data/index.js | 0 .../{admin => }/src/features/data/reducer.js | 0 .../src/features/data/resolvers.js | 0 .../src/features/data/selectors.js | 0 .../{admin => }/src/features/index.js | 0 .../{admin => }/src/index.js | 0 .../{admin => }/src/index.scss | 0 .../{admin => }/src/options/OptionEditor.js | 0 .../src/options/data/action-types.js | 0 .../{admin => }/src/options/data/actions.js | 0 .../{admin => }/src/options/data/constants.js | 0 .../{admin => }/src/options/data/index.js | 0 .../{admin => }/src/options/data/reducer.js | 0 .../{admin => }/src/options/data/resolvers.js | 0 .../{admin => }/src/options/data/selectors.js | 0 .../{admin => }/src/options/index.js | 0 .../src/rest-api-filters/data/action-types.js | 0 .../src/rest-api-filters/data/actions.js | 0 .../src/rest-api-filters/data/constants.js | 0 .../src/rest-api-filters/data/index.js | 0 .../src/rest-api-filters/data/reducer.js | 0 .../src/rest-api-filters/data/resolvers.js | 0 .../src/rest-api-filters/data/selectors.js | 0 .../{admin => }/src/rest-api-filters/index.js | 0 .../src/src/admin-notes/add-note.js | 136 +++++++++ .../src/src/admin-notes/admin-notes.js | 16 ++ .../src/src/admin-notes/delete-all-notes.js | 69 +++++ .../src/src/admin-notes/index.js | 1 + .../src/src/app/app.js | 72 +++++ .../src/src/app/index.js | 1 + .../src/src/experiments/NewExperimentForm.js | 56 ++++ .../src/src/experiments/data/action-types.js | 8 + .../src/src/experiments/data/actions.js | 105 +++++++ .../src/src/experiments/data/constants.js | 6 + .../src/experiments}/data/index.js | 0 .../src/src/experiments/data/reducer.js | 72 +++++ .../src/src/experiments/data/resolvers.js | 68 +++++ .../src/src/experiments/data/selectors.js | 3 + .../src/src/experiments/index.js | 110 ++++++++ .../src/src/features/data/action-types.js | 7 + .../src/src/features/data/actions.js | 57 ++++ .../src/src/features/data/constants.js | 3 + .../src/src/features/data/index.js | 22 ++ .../src/src/features/data/reducer.js | 28 ++ .../src/src/features/data/resolvers.js | 38 +++ .../src/src/features/data/selectors.js | 7 + .../src/src/features/index.js | 72 +++++ .../woocommerce-beta-tester/src/src/index.js | 18 ++ .../src/src/index.scss | 155 +++++++++++ .../src/src/options/OptionEditor.js | 48 ++++ .../src/src/options/data/action-types.js | 9 + .../src/src/options/data/actions.js | 81 ++++++ .../src/src/options/data/constants.js | 2 + .../src/src/options/data/index.js | 22 ++ .../src/src/options/data/reducer.js | 60 ++++ .../src/src/options/data/resolvers.js | 61 +++++ .../src/src/options/data/selectors.js | 15 + .../src/src/options/index.js | 257 ++++++++++++++++++ .../src/rest-api-filters/data/action-types.js | 9 + .../src/src/rest-api-filters/data/actions.js | 93 +++++++ .../src/rest-api-filters/data/constants.js | 5 + .../src/src/rest-api-filters/data/index.js | 22 ++ .../src/src/rest-api-filters/data/reducer.js | 55 ++++ .../src/rest-api-filters/data/resolvers.js | 29 ++ .../src/rest-api-filters/data/selectors.js | 7 + .../src/src/rest-api-filters/index.js | 182 +++++++++++++ .../{admin => src}/src/tools/README.md | 0 .../src/tools/commands/disable-email.js | 0 .../src/tools/commands/index.js | 0 .../src/tools/commands/trigger-cron.js | 0 .../commands/trigger-update-callbacks.js | 0 .../src/tools/data/action-types.js | 0 .../{admin => src}/src/tools/data/actions.js | 0 .../src/tools/data/constants.js | 0 .../src/src/tools/data/index.js | 22 ++ .../{admin => src}/src/tools/data/reducer.js | 0 .../src/tools/data/resolvers.js | 0 .../src/tools/data/selectors.js | 0 .../{admin => src}/src/tools/index.js | 0 .../src/tools/README.md | 17 ++ .../src/tools/commands/disable-email.js | 37 +++ .../src/tools/commands/index.js | 67 +++++ .../src/tools/commands/trigger-cron.js | 48 ++++ .../commands/trigger-update-callbacks.js | 51 ++++ .../src/tools/data/action-types.js | 13 + .../src/tools/data/actions.js | 211 ++++++++++++++ .../src/tools/data/constants.js | 2 + .../src/tools/data/index.js | 22 ++ .../src/tools/data/reducer.js | 88 ++++++ .../src/tools/data/resolvers.js | 57 ++++ .../src/tools/data/selectors.js | 27 ++ .../src/tools/index.js | 87 ++++++ .../woocommerce-beta-tester/webpack.config.js | 13 + 145 files changed, 3556 insertions(+) rename plugins/woocommerce-beta-tester/{admin => }/api/admin-notes/add-note.php (100%) rename plugins/woocommerce-beta-tester/{admin => }/api/admin-notes/delete-all-notes.php (100%) rename plugins/woocommerce-beta-tester/{admin => }/api/api.php (100%) create mode 100644 plugins/woocommerce-beta-tester/api/api/admin-notes/add-note.php create mode 100644 plugins/woocommerce-beta-tester/api/api/admin-notes/delete-all-notes.php create mode 100644 plugins/woocommerce-beta-tester/api/api/api.php rename plugins/woocommerce-beta-tester/{admin => api}/api/features/features.php (100%) rename plugins/woocommerce-beta-tester/{admin => api}/api/options/rest-api.php (100%) rename plugins/woocommerce-beta-tester/{admin => api}/api/rest-api-filters/hook.php (100%) rename plugins/woocommerce-beta-tester/{admin => api}/api/rest-api-filters/rest-api-filters.php (100%) rename plugins/woocommerce-beta-tester/{admin => api}/api/tools/delete-all-products.php (100%) rename plugins/woocommerce-beta-tester/{admin => api}/api/tools/disable-wc-email.php (100%) rename plugins/woocommerce-beta-tester/{admin => api}/api/tools/run-wc-admin-daily.php (100%) rename plugins/woocommerce-beta-tester/{admin => api}/api/tools/trigger-cron-job.php (100%) rename plugins/woocommerce-beta-tester/{admin => api}/api/tools/trigger-update-callbacks.php (100%) rename plugins/woocommerce-beta-tester/{admin => api}/api/tools/trigger-wca-install.php (100%) rename plugins/woocommerce-beta-tester/{admin => api}/api/tracks/tracks-debug-log.php (100%) create mode 100644 plugins/woocommerce-beta-tester/api/features/features.php create mode 100644 plugins/woocommerce-beta-tester/api/options/rest-api.php create mode 100644 plugins/woocommerce-beta-tester/api/rest-api-filters/hook.php create mode 100644 plugins/woocommerce-beta-tester/api/rest-api-filters/rest-api-filters.php create mode 100644 plugins/woocommerce-beta-tester/api/tools/delete-all-products.php create mode 100644 plugins/woocommerce-beta-tester/api/tools/disable-wc-email.php create mode 100644 plugins/woocommerce-beta-tester/api/tools/run-wc-admin-daily.php create mode 100644 plugins/woocommerce-beta-tester/api/tools/trigger-cron-job.php create mode 100644 plugins/woocommerce-beta-tester/api/tools/trigger-update-callbacks.php create mode 100644 plugins/woocommerce-beta-tester/api/tools/trigger-wca-install.php create mode 100644 plugins/woocommerce-beta-tester/api/tracks/tracks-debug-log.php rename plugins/woocommerce-beta-tester/{admin => }/images/admin-notes/banner.jpg (100%) rename plugins/woocommerce-beta-tester/{admin => }/images/admin-notes/thumbnail.jpg (100%) rename plugins/woocommerce-beta-tester/{admin => }/images/admin-notes/woocommerce-logo-vector.png (100%) create mode 100644 plugins/woocommerce-beta-tester/images/images/admin-notes/banner.jpg create mode 100644 plugins/woocommerce-beta-tester/images/images/admin-notes/thumbnail.jpg create mode 100644 plugins/woocommerce-beta-tester/images/images/admin-notes/woocommerce-logo-vector.png rename plugins/woocommerce-beta-tester/{admin => }/src/admin-notes/add-note.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/admin-notes/admin-notes.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/admin-notes/delete-all-notes.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/admin-notes/index.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/app/app.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/app/index.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/experiments/NewExperimentForm.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/experiments/data/action-types.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/experiments/data/actions.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/experiments/data/constants.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/experiments/data/index.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/experiments/data/reducer.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/experiments/data/resolvers.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/experiments/data/selectors.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/experiments/index.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/features/data/action-types.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/features/data/actions.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/features/data/constants.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/features/data/index.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/features/data/reducer.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/features/data/resolvers.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/features/data/selectors.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/features/index.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/index.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/index.scss (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/options/OptionEditor.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/options/data/action-types.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/options/data/actions.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/options/data/constants.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/options/data/index.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/options/data/reducer.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/options/data/resolvers.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/options/data/selectors.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/options/index.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/rest-api-filters/data/action-types.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/rest-api-filters/data/actions.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/rest-api-filters/data/constants.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/rest-api-filters/data/index.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/rest-api-filters/data/reducer.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/rest-api-filters/data/resolvers.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/rest-api-filters/data/selectors.js (100%) rename plugins/woocommerce-beta-tester/{admin => }/src/rest-api-filters/index.js (100%) create mode 100644 plugins/woocommerce-beta-tester/src/src/admin-notes/add-note.js create mode 100644 plugins/woocommerce-beta-tester/src/src/admin-notes/admin-notes.js create mode 100644 plugins/woocommerce-beta-tester/src/src/admin-notes/delete-all-notes.js create mode 100644 plugins/woocommerce-beta-tester/src/src/admin-notes/index.js create mode 100644 plugins/woocommerce-beta-tester/src/src/app/app.js create mode 100644 plugins/woocommerce-beta-tester/src/src/app/index.js create mode 100644 plugins/woocommerce-beta-tester/src/src/experiments/NewExperimentForm.js create mode 100644 plugins/woocommerce-beta-tester/src/src/experiments/data/action-types.js create mode 100644 plugins/woocommerce-beta-tester/src/src/experiments/data/actions.js create mode 100644 plugins/woocommerce-beta-tester/src/src/experiments/data/constants.js rename plugins/woocommerce-beta-tester/{admin/src/tools => src/src/experiments}/data/index.js (100%) create mode 100644 plugins/woocommerce-beta-tester/src/src/experiments/data/reducer.js create mode 100644 plugins/woocommerce-beta-tester/src/src/experiments/data/resolvers.js create mode 100644 plugins/woocommerce-beta-tester/src/src/experiments/data/selectors.js create mode 100644 plugins/woocommerce-beta-tester/src/src/experiments/index.js create mode 100644 plugins/woocommerce-beta-tester/src/src/features/data/action-types.js create mode 100644 plugins/woocommerce-beta-tester/src/src/features/data/actions.js create mode 100644 plugins/woocommerce-beta-tester/src/src/features/data/constants.js create mode 100644 plugins/woocommerce-beta-tester/src/src/features/data/index.js create mode 100644 plugins/woocommerce-beta-tester/src/src/features/data/reducer.js create mode 100644 plugins/woocommerce-beta-tester/src/src/features/data/resolvers.js create mode 100644 plugins/woocommerce-beta-tester/src/src/features/data/selectors.js create mode 100644 plugins/woocommerce-beta-tester/src/src/features/index.js create mode 100644 plugins/woocommerce-beta-tester/src/src/index.js create mode 100644 plugins/woocommerce-beta-tester/src/src/index.scss create mode 100644 plugins/woocommerce-beta-tester/src/src/options/OptionEditor.js create mode 100644 plugins/woocommerce-beta-tester/src/src/options/data/action-types.js create mode 100644 plugins/woocommerce-beta-tester/src/src/options/data/actions.js create mode 100644 plugins/woocommerce-beta-tester/src/src/options/data/constants.js create mode 100644 plugins/woocommerce-beta-tester/src/src/options/data/index.js create mode 100644 plugins/woocommerce-beta-tester/src/src/options/data/reducer.js create mode 100644 plugins/woocommerce-beta-tester/src/src/options/data/resolvers.js create mode 100644 plugins/woocommerce-beta-tester/src/src/options/data/selectors.js create mode 100644 plugins/woocommerce-beta-tester/src/src/options/index.js create mode 100644 plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/action-types.js create mode 100644 plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/actions.js create mode 100644 plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/constants.js create mode 100644 plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/index.js create mode 100644 plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/reducer.js create mode 100644 plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/resolvers.js create mode 100644 plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/selectors.js create mode 100644 plugins/woocommerce-beta-tester/src/src/rest-api-filters/index.js rename plugins/woocommerce-beta-tester/{admin => src}/src/tools/README.md (100%) rename plugins/woocommerce-beta-tester/{admin => src}/src/tools/commands/disable-email.js (100%) rename plugins/woocommerce-beta-tester/{admin => src}/src/tools/commands/index.js (100%) rename plugins/woocommerce-beta-tester/{admin => src}/src/tools/commands/trigger-cron.js (100%) rename plugins/woocommerce-beta-tester/{admin => src}/src/tools/commands/trigger-update-callbacks.js (100%) rename plugins/woocommerce-beta-tester/{admin => src}/src/tools/data/action-types.js (100%) rename plugins/woocommerce-beta-tester/{admin => src}/src/tools/data/actions.js (100%) rename plugins/woocommerce-beta-tester/{admin => src}/src/tools/data/constants.js (100%) create mode 100644 plugins/woocommerce-beta-tester/src/src/tools/data/index.js rename plugins/woocommerce-beta-tester/{admin => src}/src/tools/data/reducer.js (100%) rename plugins/woocommerce-beta-tester/{admin => src}/src/tools/data/resolvers.js (100%) rename plugins/woocommerce-beta-tester/{admin => src}/src/tools/data/selectors.js (100%) rename plugins/woocommerce-beta-tester/{admin => src}/src/tools/index.js (100%) create mode 100644 plugins/woocommerce-beta-tester/src/tools/README.md create mode 100644 plugins/woocommerce-beta-tester/src/tools/commands/disable-email.js create mode 100644 plugins/woocommerce-beta-tester/src/tools/commands/index.js create mode 100644 plugins/woocommerce-beta-tester/src/tools/commands/trigger-cron.js create mode 100644 plugins/woocommerce-beta-tester/src/tools/commands/trigger-update-callbacks.js create mode 100644 plugins/woocommerce-beta-tester/src/tools/data/action-types.js create mode 100644 plugins/woocommerce-beta-tester/src/tools/data/actions.js create mode 100644 plugins/woocommerce-beta-tester/src/tools/data/constants.js create mode 100644 plugins/woocommerce-beta-tester/src/tools/data/index.js create mode 100644 plugins/woocommerce-beta-tester/src/tools/data/reducer.js create mode 100644 plugins/woocommerce-beta-tester/src/tools/data/resolvers.js create mode 100644 plugins/woocommerce-beta-tester/src/tools/data/selectors.js create mode 100644 plugins/woocommerce-beta-tester/src/tools/index.js create mode 100644 plugins/woocommerce-beta-tester/webpack.config.js diff --git a/plugins/woocommerce-beta-tester/admin/api/admin-notes/add-note.php b/plugins/woocommerce-beta-tester/api/admin-notes/add-note.php similarity index 100% rename from plugins/woocommerce-beta-tester/admin/api/admin-notes/add-note.php rename to plugins/woocommerce-beta-tester/api/admin-notes/add-note.php diff --git a/plugins/woocommerce-beta-tester/admin/api/admin-notes/delete-all-notes.php b/plugins/woocommerce-beta-tester/api/admin-notes/delete-all-notes.php similarity index 100% rename from plugins/woocommerce-beta-tester/admin/api/admin-notes/delete-all-notes.php rename to plugins/woocommerce-beta-tester/api/admin-notes/delete-all-notes.php diff --git a/plugins/woocommerce-beta-tester/admin/api/api.php b/plugins/woocommerce-beta-tester/api/api.php similarity index 100% rename from plugins/woocommerce-beta-tester/admin/api/api.php rename to plugins/woocommerce-beta-tester/api/api.php diff --git a/plugins/woocommerce-beta-tester/api/api/admin-notes/add-note.php b/plugins/woocommerce-beta-tester/api/api/admin-notes/add-note.php new file mode 100644 index 00000000000..ef693750289 --- /dev/null +++ b/plugins/woocommerce-beta-tester/api/api/admin-notes/add-note.php @@ -0,0 +1,67 @@ +get_param( 'type' ); + $layout = $request->get_param( 'layout' ); + + $note->set_name( $request->get_param( 'name' ) ); + $note->set_title( $request->get_param( 'title' ) ); + $note->set_content( $mock_note_data[ 'content' ] ); + $note->set_image( $mock_note_data[ $type ][ $layout ] ); + $note->set_layout( $layout ); + $note->set_type( $type ); + possibly_add_action( $note ); + + if ( 'email' === $type ) { + add_email_note_params( $note ); + } + + $note->save(); + + return true; +} + +function add_email_note_params( $note ) { + $additional_data = array( + 'role' => 'administrator', + ); + $note->set_content_data( (object) $additional_data ); +} + +function possibly_add_action( $note ) { + if ( $note->get_type() === 'info' ) { + return; + } + $action_name = sprintf( + 'test-action-%s', + $note->get_name() + ); + $note->add_action( $action_name, 'Test action', wc_admin_url() ); +} + +function get_mock_note_data() { + $plugin_url = site_url() . '/wp-content/plugins/woocommerce-admin-test-helper/'; + return array( + 'content' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud.', + 'info' => array( + 'banner' => $plugin_url . 'images/admin-notes/banner.jpg', + 'thumbnail' => $plugin_url . 'images/admin-notes/thumbnail.jpg', + 'plain' => '' + ), + 'email' => array( + 'plain' => $plugin_url . 'images/admin-notes/woocommerce-logo-vector.png' + ), + 'update' => array( + 'plain' => '' + ) + ); +} diff --git a/plugins/woocommerce-beta-tester/api/api/admin-notes/delete-all-notes.php b/plugins/woocommerce-beta-tester/api/api/admin-notes/delete-all-notes.php new file mode 100644 index 00000000000..38195c63c66 --- /dev/null +++ b/plugins/woocommerce-beta-tester/api/api/admin-notes/delete-all-notes.php @@ -0,0 +1,18 @@ +query( "DELETE FROM {$wpdb->prefix}wc_admin_notes" ); + $deleted_action_count = $wpdb->query( "DELETE FROM {$wpdb->prefix}wc_admin_note_actions" ); + + return array( + 'deleted_note_count' => $deleted_note_count, + 'deleted_action_count' => $deleted_action_count, + ); +} + diff --git a/plugins/woocommerce-beta-tester/api/api/api.php b/plugins/woocommerce-beta-tester/api/api/api.php new file mode 100644 index 00000000000..36614cbe251 --- /dev/null +++ b/plugins/woocommerce-beta-tester/api/api/api.php @@ -0,0 +1,41 @@ + 'POST', + 'callback' => $callback, + 'permission_callback' => function( $request ) { + if ( ! wc_rest_check_manager_permissions( 'settings', 'edit' ) ) { + return new \WP_Error( + 'woocommerce_rest_cannot_edit', + __( 'Sorry, you cannot perform this action', 'woocommerce-admin-test-helper' ) + ); + } + return true; + }, + ); + + $default_options = array_merge( $default_options, $additional_options ); + + register_rest_route( + 'wc-admin-test-helper', + $route, + $default_options + ); + } ); +} + +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'); +require( 'tools/disable-wc-email.php' ); +require( 'tools/trigger-update-callbacks.php' ); +require( 'tracks/tracks-debug-log.php' ); +require( 'features/features.php' ); +require( 'rest-api-filters/rest-api-filters.php' ); +require( 'rest-api-filters/hook.php' ); diff --git a/plugins/woocommerce-beta-tester/admin/api/features/features.php b/plugins/woocommerce-beta-tester/api/api/features/features.php similarity index 100% rename from plugins/woocommerce-beta-tester/admin/api/features/features.php rename to plugins/woocommerce-beta-tester/api/api/features/features.php diff --git a/plugins/woocommerce-beta-tester/admin/api/options/rest-api.php b/plugins/woocommerce-beta-tester/api/api/options/rest-api.php similarity index 100% rename from plugins/woocommerce-beta-tester/admin/api/options/rest-api.php rename to plugins/woocommerce-beta-tester/api/api/options/rest-api.php diff --git a/plugins/woocommerce-beta-tester/admin/api/rest-api-filters/hook.php b/plugins/woocommerce-beta-tester/api/api/rest-api-filters/hook.php similarity index 100% rename from plugins/woocommerce-beta-tester/admin/api/rest-api-filters/hook.php rename to plugins/woocommerce-beta-tester/api/api/rest-api-filters/hook.php diff --git a/plugins/woocommerce-beta-tester/admin/api/rest-api-filters/rest-api-filters.php b/plugins/woocommerce-beta-tester/api/api/rest-api-filters/rest-api-filters.php similarity index 100% rename from plugins/woocommerce-beta-tester/admin/api/rest-api-filters/rest-api-filters.php rename to plugins/woocommerce-beta-tester/api/api/rest-api-filters/rest-api-filters.php diff --git a/plugins/woocommerce-beta-tester/admin/api/tools/delete-all-products.php b/plugins/woocommerce-beta-tester/api/api/tools/delete-all-products.php similarity index 100% rename from plugins/woocommerce-beta-tester/admin/api/tools/delete-all-products.php rename to plugins/woocommerce-beta-tester/api/api/tools/delete-all-products.php diff --git a/plugins/woocommerce-beta-tester/admin/api/tools/disable-wc-email.php b/plugins/woocommerce-beta-tester/api/api/tools/disable-wc-email.php similarity index 100% rename from plugins/woocommerce-beta-tester/admin/api/tools/disable-wc-email.php rename to plugins/woocommerce-beta-tester/api/api/tools/disable-wc-email.php diff --git a/plugins/woocommerce-beta-tester/admin/api/tools/run-wc-admin-daily.php b/plugins/woocommerce-beta-tester/api/api/tools/run-wc-admin-daily.php similarity index 100% rename from plugins/woocommerce-beta-tester/admin/api/tools/run-wc-admin-daily.php rename to plugins/woocommerce-beta-tester/api/api/tools/run-wc-admin-daily.php diff --git a/plugins/woocommerce-beta-tester/admin/api/tools/trigger-cron-job.php b/plugins/woocommerce-beta-tester/api/api/tools/trigger-cron-job.php similarity index 100% rename from plugins/woocommerce-beta-tester/admin/api/tools/trigger-cron-job.php rename to plugins/woocommerce-beta-tester/api/api/tools/trigger-cron-job.php diff --git a/plugins/woocommerce-beta-tester/admin/api/tools/trigger-update-callbacks.php b/plugins/woocommerce-beta-tester/api/api/tools/trigger-update-callbacks.php similarity index 100% rename from plugins/woocommerce-beta-tester/admin/api/tools/trigger-update-callbacks.php rename to plugins/woocommerce-beta-tester/api/api/tools/trigger-update-callbacks.php diff --git a/plugins/woocommerce-beta-tester/admin/api/tools/trigger-wca-install.php b/plugins/woocommerce-beta-tester/api/api/tools/trigger-wca-install.php similarity index 100% rename from plugins/woocommerce-beta-tester/admin/api/tools/trigger-wca-install.php rename to plugins/woocommerce-beta-tester/api/api/tools/trigger-wca-install.php diff --git a/plugins/woocommerce-beta-tester/admin/api/tracks/tracks-debug-log.php b/plugins/woocommerce-beta-tester/api/api/tracks/tracks-debug-log.php similarity index 100% rename from plugins/woocommerce-beta-tester/admin/api/tracks/tracks-debug-log.php rename to plugins/woocommerce-beta-tester/api/api/tracks/tracks-debug-log.php diff --git a/plugins/woocommerce-beta-tester/api/features/features.php b/plugins/woocommerce-beta-tester/api/features/features.php new file mode 100644 index 00000000000..f19db0cb94f --- /dev/null +++ b/plugins/woocommerce-beta-tester/api/features/features.php @@ -0,0 +1,59 @@ +[a-z0-9_\-]+)/toggle', + 'toggle_feature', + array( + 'methods' => 'POST', + ) +); + +register_woocommerce_admin_test_helper_rest_route( + '/features', + 'get_features', + array( + 'methods' => 'GET', + ) +); + +register_woocommerce_admin_test_helper_rest_route( + '/features/reset', + 'reset_features', + array( + 'methods' => 'POST', + ) +); + +function toggle_feature( $request ) { + $features = get_features(); + $custom_feature_values = get_option( OPTION_NAME_PREFIX, array() ); + $feature_name = $request->get_param( 'feature_name' ); + + if ( ! isset( $features[$feature_name ]) ) { + return new WP_REST_Response( $features, 204 ); + } + + if ( isset( $custom_feature_values[$feature_name] ) ) { + unset( $custom_feature_values[$feature_name] ); + } else { + $custom_feature_values[$feature_name] = ! $features[ $feature_name ]; + } + + update_option(OPTION_NAME_PREFIX, $custom_feature_values ); + return new WP_REST_Response( get_features(), 200 ); +} + +function reset_features() { + delete_option( OPTION_NAME_PREFIX ); + return new WP_REST_Response( get_features(), 200 ); +} + +function get_features() { + if ( function_exists( 'wc_admin_get_feature_config' ) ) { + return apply_filters( 'woocommerce_admin_get_feature_config', wc_admin_get_feature_config() ); + } + return array(); +} diff --git a/plugins/woocommerce-beta-tester/api/options/rest-api.php b/plugins/woocommerce-beta-tester/api/options/rest-api.php new file mode 100644 index 00000000000..4b0c013102f --- /dev/null +++ b/plugins/woocommerce-beta-tester/api/options/rest-api.php @@ -0,0 +1,79 @@ + 'GET', + 'args' => array( + 'page' => array( + 'description' => 'Current page of the collection.', + 'type' => 'integer', + 'default' => 1, + 'sanitize_callback' => 'absint', + ), + 'per_page' => array( + 'description' => 'Maximum number of items to be returned in result set.', + 'type' => 'integer', + 'default' => 10, + 'sanitize_callback' => 'absint', + ), + 'search' => array( + 'description' => 'Limit results to those matching a string.', + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + ) +); + +register_woocommerce_admin_test_helper_rest_route( + '/options/(?P(.*)+)', + 'wca_test_helper_delete_option', + array( + 'methods' => 'DELETE', + 'args' => array( + 'option_names' => array( + 'type' => 'string', + ), + ), + ) +); + +function wca_test_helper_delete_option( $request ) { + global $wpdb; + $option_names = explode( ',', $request->get_param( 'option_names' ) ); + $option_names = array_map( function( $option_name ) { + return "'" . $option_name . "'"; + }, $option_names ); + + $option_names = implode( ',', $option_names ); + $query = "delete from {$wpdb->prefix}options where option_name in ({$option_names})"; + $wpdb->query( $query ); + + return new WP_REST_RESPONSE( null, 204 ); +} + +function wca_test_helper_get_options( $request ) { + global $wpdb; + + $per_page = $request->get_param( 'per_page' ); + $page = $request->get_param( 'page' ); + $search = $request->get_param( 'search' ); + + $query = " + select option_id, option_name, option_value, autoload + from {$wpdb->prefix}options + "; + + if ( $search ) { + $query .= "where option_name like '%{$search}%'"; + } + + $query .= ' order by option_id desc limit 30'; + + $options = $wpdb->get_results( $query ); + + return new WP_REST_Response( $options, 200 ); +} + diff --git a/plugins/woocommerce-beta-tester/api/rest-api-filters/hook.php b/plugins/woocommerce-beta-tester/api/rest-api-filters/hook.php new file mode 100644 index 00000000000..65f331ee829 --- /dev/null +++ b/plugins/woocommerce-beta-tester/api/rest-api-filters/hook.php @@ -0,0 +1,52 @@ + 1 ) { + $key = array_shift($keys); + if (! isset($array[$key]) || ! is_array($array[$key]) ) { + $array[$key] = []; + } + $array = &$array[$key]; + } + + $array[ array_shift($keys) ] = $value; + return $array; +} + +add_filter( + 'rest_request_after_callbacks', + function ( $response, array $handler, \WP_REST_Request $request ) use ( $filters ) { + if (! $response instanceof \WP_REST_Response ) { + return $response; + } + $route = $request->get_route(); + $filters = array_filter( + $filters, function ( $filter ) use ( $request, $route ) { + if ($filter['enabled'] && $filter['endpoint'] == $route ) { + return true; + } + return false; + } + ); + + $data = $response->get_data(); + + foreach ( $filters as $filter ) { + array_dot_set($data, $filter['dot_notation'], $filter['replacement']); + } + + $response->set_data($data); + + return $response; + }, + 10, + 3 +); \ No newline at end of file diff --git a/plugins/woocommerce-beta-tester/api/rest-api-filters/rest-api-filters.php b/plugins/woocommerce-beta-tester/api/rest-api-filters/rest-api-filters.php new file mode 100644 index 00000000000..19b06c9a722 --- /dev/null +++ b/plugins/woocommerce-beta-tester/api/rest-api-filters/rest-api-filters.php @@ -0,0 +1,109 @@ + 'POST', + 'args' => array( + 'endpoint' => array( + 'description' => 'Rest API endpoint.', + 'type' => 'string', + 'required' => true, + 'sanitize_callback' => 'sanitize_text_field', + ), + 'dot_notation' => array( + 'description' => 'Dot notation of the target field.', + 'type' => 'string', + 'required' => true, + 'sanitize_callback' => 'sanitize_text_field', + ), + 'replacement' => array( + 'description' => 'Replacement value for the target field.', + 'type' => 'string', + 'required' => true, + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + ) +); + +register_woocommerce_admin_test_helper_rest_route( + '/rest-api-filters', + [ WCA_Test_Helper_Rest_Api_Filters::class, 'delete' ], + array( + 'methods' => 'DELETE', + 'args' => array( + 'index' => array( + 'description' => 'Rest API endpoint.', + 'type' => 'integer', + 'required' => true, + ), + ), + ) +); + + +register_woocommerce_admin_test_helper_rest_route( + '/rest-api-filters/(?P\d+)/toggle', + [ WCA_Test_Helper_Rest_Api_Filters::class, 'toggle' ], + array( + 'methods' => 'POST', + ) +); + + +class WCA_Test_Helper_Rest_Api_Filters { + const WC_ADMIN_TEST_HELPER_REST_API_FILTER_OPTION = 'wc-admin-test-helper-rest-api-filters'; + + public static function create( $request ) { + $endpoint = $request->get_param('endpoint'); + $dot_notation = $request->get_param('dot_notation'); + $replacement = $request->get_param('replacement'); + + self::update( + function ( $filters ) use ( + $endpoint, + $dot_notation, + $replacement + ) { + $filters[] = array( + 'endpoint' => $endpoint, + 'dot_notation' => $dot_notation, + 'replacement' => filter_var( $replacement, FILTER_VALIDATE_BOOLEAN ), + 'enabled' => true, + ); + return $filters; + } + ); + return new WP_REST_RESPONSE(null, 204); + } + + public static function update( callable $callback ) { + $filters = get_option(self::WC_ADMIN_TEST_HELPER_REST_API_FILTER_OPTION, array()); + $filters = $callback( $filters ); + return update_option(self::WC_ADMIN_TEST_HELPER_REST_API_FILTER_OPTION, $filters); + } + + public static function delete( $request ) { + self::update( + function ( $filters ) use ( $request ) { + array_splice($filters, $request->get_param('index'), 1); + return $filters; + } + ); + + return new WP_REST_RESPONSE(null, 204); + } + + public static function toggle( $request ) { + self::update( + function ( $filters ) use ( $request ) { + $index = $request->get_param('index'); + $filters[$index]['enabled'] = !$filters[$index]['enabled']; + return $filters; + } + ); + return new WP_REST_RESPONSE(null, 204); + } +} \ No newline at end of file diff --git a/plugins/woocommerce-beta-tester/api/tools/delete-all-products.php b/plugins/woocommerce-beta-tester/api/tools/delete-all-products.php new file mode 100644 index 00000000000..953f7e11e3b --- /dev/null +++ b/plugins/woocommerce-beta-tester/api/tools/delete-all-products.php @@ -0,0 +1,16 @@ +get_products(); + + foreach ( $products as $product ) { + $product->delete( true ); + } + + return true; +} diff --git a/plugins/woocommerce-beta-tester/api/tools/disable-wc-email.php b/plugins/woocommerce-beta-tester/api/tools/disable-wc-email.php new file mode 100644 index 00000000000..7997195fbc7 --- /dev/null +++ b/plugins/woocommerce-beta-tester/api/tools/disable-wc-email.php @@ -0,0 +1,46 @@ + 'GET', + ) +); + +function toggle_emails() { + $emails_disabled = 'yes'; + if ( $emails_disabled === get_option( 'wc_admin_test_helper_email_disabled', 'no' ) ) { + $emails_disabled = 'no'; + remove_filter('woocommerce_email_get_option', 'disable_wc_emails' ); + } + update_option('wc_admin_test_helper_email_disabled', $emails_disabled ); + return new WP_REST_Response( $emails_disabled, 200 ); +} + +function get_email_status() { + $emails_disabled = get_option( 'wc_admin_test_helper_email_disabled', 'no' ); + return new WP_REST_Response( $emails_disabled, 200 ); +} + +if ( 'yes' === get_option( 'wc_admin_test_helper_email_disabled', 'no' ) ) { + add_filter('woocommerce_email_get_option', 'disable_wc_emails' ); + add_action( 'woocommerce_email', 'unhook_other_wc_emails' ); +} + +function disable_wc_emails( $key ) { + if ( $key === 'enabled' ) { + return false; + } +} + +function unhook_other_wc_emails( $email ) { + remove_action( 'woocommerce_low_stock_notification', array( $email, 'low_stock' ) ); + remove_action( 'woocommerce_no_stock_notification', array( $email, 'no_stock' ) ); + remove_action( 'woocommerce_product_on_backorder_notification', array( $email, 'backorder' ) ); + remove_action( 'woocommerce_new_customer_note_notification', array( $email->emails['WC_Email_Customer_Note'], 'trigger' ) ); +} diff --git a/plugins/woocommerce-beta-tester/api/tools/run-wc-admin-daily.php b/plugins/woocommerce-beta-tester/api/tools/run-wc-admin-daily.php new file mode 100644 index 00000000000..70f7ac5a24a --- /dev/null +++ b/plugins/woocommerce-beta-tester/api/tools/run-wc-admin-daily.php @@ -0,0 +1,11 @@ + 'GET', + ) +); +register_woocommerce_admin_test_helper_rest_route( + '/tools/trigger-selected-cron/v1', + 'trigger_selected_cron', + array( + 'methods' => '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/plugins/woocommerce-beta-tester/api/tools/trigger-update-callbacks.php b/plugins/woocommerce-beta-tester/api/tools/trigger-update-callbacks.php new file mode 100644 index 00000000000..3d1c0d103f3 --- /dev/null +++ b/plugins/woocommerce-beta-tester/api/tools/trigger-update-callbacks.php @@ -0,0 +1,47 @@ + 'GET', + ) +); +register_woocommerce_admin_test_helper_rest_route( + '/tools/trigger-selected-update-callbacks/v1', + 'trigger_selected_update_callbacks', + array( + 'methods' => 'POST', + 'args' => array( + 'version' => array( + 'description' => 'Name of the update version', + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + ) + ), + ) +); + +function tools_get_wc_admin_versions() { + $db_updates = \WC_Install::get_db_update_callbacks(); + + return new WP_REST_Response( array_keys( $db_updates ), 200 ); +} + +function trigger_selected_update_callbacks( $request ) { + $version = $request->get_param( 'version' ); + if ( ! isset( $version ) ) { + return; + } + + $db_updates = \WC_Install::get_db_update_callbacks(); + $update_callbacks = $db_updates[ $version ]; + + foreach ( $update_callbacks as $update_callback ) { + call_user_func( $update_callback ); + } + + return false; +} diff --git a/plugins/woocommerce-beta-tester/api/tools/trigger-wca-install.php b/plugins/woocommerce-beta-tester/api/tools/trigger-wca-install.php new file mode 100644 index 00000000000..43c1121f577 --- /dev/null +++ b/plugins/woocommerce-beta-tester/api/tools/trigger-wca-install.php @@ -0,0 +1,11 @@ +logger = $logger; + $this->logger = $logger; + } + + /** + * Log the event. + * + * @param array $properties Event properties. + * @param string $event_name Event name. + */ + public function log_event( $properties, $event_name ) { + $this->logger->debug( + $event_name, + array( 'source' => $this->source ) + ); + foreach ( $properties as $key => $property ) { + $this->logger->debug( + " - {$key}: {$property}", + array( 'source' => $this->source ) + ); + } + + return $properties; + } +} + +new TracksDebugLog(); diff --git a/plugins/woocommerce-beta-tester/admin/images/admin-notes/banner.jpg b/plugins/woocommerce-beta-tester/images/admin-notes/banner.jpg similarity index 100% rename from plugins/woocommerce-beta-tester/admin/images/admin-notes/banner.jpg rename to plugins/woocommerce-beta-tester/images/admin-notes/banner.jpg diff --git a/plugins/woocommerce-beta-tester/admin/images/admin-notes/thumbnail.jpg b/plugins/woocommerce-beta-tester/images/admin-notes/thumbnail.jpg similarity index 100% rename from plugins/woocommerce-beta-tester/admin/images/admin-notes/thumbnail.jpg rename to plugins/woocommerce-beta-tester/images/admin-notes/thumbnail.jpg diff --git a/plugins/woocommerce-beta-tester/admin/images/admin-notes/woocommerce-logo-vector.png b/plugins/woocommerce-beta-tester/images/admin-notes/woocommerce-logo-vector.png similarity index 100% rename from plugins/woocommerce-beta-tester/admin/images/admin-notes/woocommerce-logo-vector.png rename to plugins/woocommerce-beta-tester/images/admin-notes/woocommerce-logo-vector.png diff --git a/plugins/woocommerce-beta-tester/images/images/admin-notes/banner.jpg b/plugins/woocommerce-beta-tester/images/images/admin-notes/banner.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1959ae3e6d1cab3a69cad46c21f29e7966a8bb35 GIT binary patch literal 10921 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�?+bKCE18c&zw=qK0-U z6ucul>#YdM&n%YR?bSe?_u*V-vf9mwp9ewLwcf8+<Q z4>hm<_89J0sNC`VMa?sLNNYm!qPh1mUb?C)^8y zluRqFIQUX?Vt8=6@zOOIE->JhH+7lOZs1wTS5|nz@kj%51Kyl2b)w1ZV>l=mLEOU7 zSJ_|{^Had^Z!Vs{kF=n)Cfgy;_f60ppQmLj?miZd&Wfoh% zJWW4aS*heiJ(%CL$1T8P0(ZTeP1|)UXD*YpXM2S!EMIp0)zF=$EE9$7g+a2~jg+A8 z2I215O(d6zfaH{#lvPp5%dl8iR2I=wuW$fl_B&dJ;LU#& zGeeF2$KVt>7&%(?h&K|-a9YFN^GcQ>1E>-4SpRqqH_ z8)m>cU-1jDz^Ww3#Byl?cL&YH>Jw8wzblLT&U1hF zQd$YxfgKa#)Q8U{Fy!~Y4pz(uZNPlcSO27b?XT1&f-HT0*P~)#nU_n4Dj3E|+)!n0 zYcv@yj39R8#B1Sq_1{fBj0IkES7C^>u|Tf_3cimyK;AfE!UEzK8Ru_vn2}3`i#lu0 z>FqP+edO*eb;Ai~UySTftMs&ale3w|9g>GFIZ19_MBP`wtuYc}lwY_WizNnxiU|-z zmAO7|r%|vg^EDfsJRR*6nDhH^TX%He$j>FVQ0hbGLa1zyvo4u=3mcRF?YE9CfmNvd za1cnc9ffa(1(uE4(NF6C!Vj;>I21j!j|DZ@+P20rCSJ?Y#dp-uC58vx8m{x^3_8Zm!jiN&QmI zjeVd2m?~(pMxIS^Lb#EX&#?P1Zw5f3>{4+&E&&z&K+#p7g8(0oS{y^&fo> p|I8~H@DI@||Jp1MZ7leYLu39Wq=w`lZbScdw*QAwaZ1>k{{piqVBr7& literal 0 HcmV?d00001 diff --git a/plugins/woocommerce-beta-tester/images/images/admin-notes/thumbnail.jpg b/plugins/woocommerce-beta-tester/images/images/admin-notes/thumbnail.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f08bbc9f62d4407c877821bb2194107921570d53 GIT binary patch literal 21461 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 literal 0 HcmV?d00001 diff --git a/plugins/woocommerce-beta-tester/images/images/admin-notes/woocommerce-logo-vector.png b/plugins/woocommerce-beta-tester/images/images/admin-notes/woocommerce-logo-vector.png new file mode 100644 index 0000000000000000000000000000000000000000..d67c209ae9983d9a155f11566641deced920a9de GIT binary patch literal 5970 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 literal 0 HcmV?d00001 diff --git a/plugins/woocommerce-beta-tester/admin/src/admin-notes/add-note.js b/plugins/woocommerce-beta-tester/src/admin-notes/add-note.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/admin-notes/add-note.js rename to plugins/woocommerce-beta-tester/src/admin-notes/add-note.js diff --git a/plugins/woocommerce-beta-tester/admin/src/admin-notes/admin-notes.js b/plugins/woocommerce-beta-tester/src/admin-notes/admin-notes.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/admin-notes/admin-notes.js rename to plugins/woocommerce-beta-tester/src/admin-notes/admin-notes.js diff --git a/plugins/woocommerce-beta-tester/admin/src/admin-notes/delete-all-notes.js b/plugins/woocommerce-beta-tester/src/admin-notes/delete-all-notes.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/admin-notes/delete-all-notes.js rename to plugins/woocommerce-beta-tester/src/admin-notes/delete-all-notes.js diff --git a/plugins/woocommerce-beta-tester/admin/src/admin-notes/index.js b/plugins/woocommerce-beta-tester/src/admin-notes/index.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/admin-notes/index.js rename to plugins/woocommerce-beta-tester/src/admin-notes/index.js diff --git a/plugins/woocommerce-beta-tester/admin/src/app/app.js b/plugins/woocommerce-beta-tester/src/app/app.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/app/app.js rename to plugins/woocommerce-beta-tester/src/app/app.js diff --git a/plugins/woocommerce-beta-tester/admin/src/app/index.js b/plugins/woocommerce-beta-tester/src/app/index.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/app/index.js rename to plugins/woocommerce-beta-tester/src/app/index.js diff --git a/plugins/woocommerce-beta-tester/admin/src/experiments/NewExperimentForm.js b/plugins/woocommerce-beta-tester/src/experiments/NewExperimentForm.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/experiments/NewExperimentForm.js rename to plugins/woocommerce-beta-tester/src/experiments/NewExperimentForm.js diff --git a/plugins/woocommerce-beta-tester/admin/src/experiments/data/action-types.js b/plugins/woocommerce-beta-tester/src/experiments/data/action-types.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/experiments/data/action-types.js rename to plugins/woocommerce-beta-tester/src/experiments/data/action-types.js diff --git a/plugins/woocommerce-beta-tester/admin/src/experiments/data/actions.js b/plugins/woocommerce-beta-tester/src/experiments/data/actions.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/experiments/data/actions.js rename to plugins/woocommerce-beta-tester/src/experiments/data/actions.js diff --git a/plugins/woocommerce-beta-tester/admin/src/experiments/data/constants.js b/plugins/woocommerce-beta-tester/src/experiments/data/constants.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/experiments/data/constants.js rename to plugins/woocommerce-beta-tester/src/experiments/data/constants.js diff --git a/plugins/woocommerce-beta-tester/admin/src/experiments/data/index.js b/plugins/woocommerce-beta-tester/src/experiments/data/index.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/experiments/data/index.js rename to plugins/woocommerce-beta-tester/src/experiments/data/index.js diff --git a/plugins/woocommerce-beta-tester/admin/src/experiments/data/reducer.js b/plugins/woocommerce-beta-tester/src/experiments/data/reducer.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/experiments/data/reducer.js rename to plugins/woocommerce-beta-tester/src/experiments/data/reducer.js diff --git a/plugins/woocommerce-beta-tester/admin/src/experiments/data/resolvers.js b/plugins/woocommerce-beta-tester/src/experiments/data/resolvers.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/experiments/data/resolvers.js rename to plugins/woocommerce-beta-tester/src/experiments/data/resolvers.js diff --git a/plugins/woocommerce-beta-tester/admin/src/experiments/data/selectors.js b/plugins/woocommerce-beta-tester/src/experiments/data/selectors.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/experiments/data/selectors.js rename to plugins/woocommerce-beta-tester/src/experiments/data/selectors.js diff --git a/plugins/woocommerce-beta-tester/admin/src/experiments/index.js b/plugins/woocommerce-beta-tester/src/experiments/index.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/experiments/index.js rename to plugins/woocommerce-beta-tester/src/experiments/index.js diff --git a/plugins/woocommerce-beta-tester/admin/src/features/data/action-types.js b/plugins/woocommerce-beta-tester/src/features/data/action-types.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/features/data/action-types.js rename to plugins/woocommerce-beta-tester/src/features/data/action-types.js diff --git a/plugins/woocommerce-beta-tester/admin/src/features/data/actions.js b/plugins/woocommerce-beta-tester/src/features/data/actions.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/features/data/actions.js rename to plugins/woocommerce-beta-tester/src/features/data/actions.js diff --git a/plugins/woocommerce-beta-tester/admin/src/features/data/constants.js b/plugins/woocommerce-beta-tester/src/features/data/constants.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/features/data/constants.js rename to plugins/woocommerce-beta-tester/src/features/data/constants.js diff --git a/plugins/woocommerce-beta-tester/admin/src/features/data/index.js b/plugins/woocommerce-beta-tester/src/features/data/index.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/features/data/index.js rename to plugins/woocommerce-beta-tester/src/features/data/index.js diff --git a/plugins/woocommerce-beta-tester/admin/src/features/data/reducer.js b/plugins/woocommerce-beta-tester/src/features/data/reducer.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/features/data/reducer.js rename to plugins/woocommerce-beta-tester/src/features/data/reducer.js diff --git a/plugins/woocommerce-beta-tester/admin/src/features/data/resolvers.js b/plugins/woocommerce-beta-tester/src/features/data/resolvers.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/features/data/resolvers.js rename to plugins/woocommerce-beta-tester/src/features/data/resolvers.js diff --git a/plugins/woocommerce-beta-tester/admin/src/features/data/selectors.js b/plugins/woocommerce-beta-tester/src/features/data/selectors.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/features/data/selectors.js rename to plugins/woocommerce-beta-tester/src/features/data/selectors.js diff --git a/plugins/woocommerce-beta-tester/admin/src/features/index.js b/plugins/woocommerce-beta-tester/src/features/index.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/features/index.js rename to plugins/woocommerce-beta-tester/src/features/index.js diff --git a/plugins/woocommerce-beta-tester/admin/src/index.js b/plugins/woocommerce-beta-tester/src/index.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/index.js rename to plugins/woocommerce-beta-tester/src/index.js diff --git a/plugins/woocommerce-beta-tester/admin/src/index.scss b/plugins/woocommerce-beta-tester/src/index.scss similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/index.scss rename to plugins/woocommerce-beta-tester/src/index.scss diff --git a/plugins/woocommerce-beta-tester/admin/src/options/OptionEditor.js b/plugins/woocommerce-beta-tester/src/options/OptionEditor.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/options/OptionEditor.js rename to plugins/woocommerce-beta-tester/src/options/OptionEditor.js diff --git a/plugins/woocommerce-beta-tester/admin/src/options/data/action-types.js b/plugins/woocommerce-beta-tester/src/options/data/action-types.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/options/data/action-types.js rename to plugins/woocommerce-beta-tester/src/options/data/action-types.js diff --git a/plugins/woocommerce-beta-tester/admin/src/options/data/actions.js b/plugins/woocommerce-beta-tester/src/options/data/actions.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/options/data/actions.js rename to plugins/woocommerce-beta-tester/src/options/data/actions.js diff --git a/plugins/woocommerce-beta-tester/admin/src/options/data/constants.js b/plugins/woocommerce-beta-tester/src/options/data/constants.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/options/data/constants.js rename to plugins/woocommerce-beta-tester/src/options/data/constants.js diff --git a/plugins/woocommerce-beta-tester/admin/src/options/data/index.js b/plugins/woocommerce-beta-tester/src/options/data/index.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/options/data/index.js rename to plugins/woocommerce-beta-tester/src/options/data/index.js diff --git a/plugins/woocommerce-beta-tester/admin/src/options/data/reducer.js b/plugins/woocommerce-beta-tester/src/options/data/reducer.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/options/data/reducer.js rename to plugins/woocommerce-beta-tester/src/options/data/reducer.js diff --git a/plugins/woocommerce-beta-tester/admin/src/options/data/resolvers.js b/plugins/woocommerce-beta-tester/src/options/data/resolvers.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/options/data/resolvers.js rename to plugins/woocommerce-beta-tester/src/options/data/resolvers.js diff --git a/plugins/woocommerce-beta-tester/admin/src/options/data/selectors.js b/plugins/woocommerce-beta-tester/src/options/data/selectors.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/options/data/selectors.js rename to plugins/woocommerce-beta-tester/src/options/data/selectors.js diff --git a/plugins/woocommerce-beta-tester/admin/src/options/index.js b/plugins/woocommerce-beta-tester/src/options/index.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/options/index.js rename to plugins/woocommerce-beta-tester/src/options/index.js diff --git a/plugins/woocommerce-beta-tester/admin/src/rest-api-filters/data/action-types.js b/plugins/woocommerce-beta-tester/src/rest-api-filters/data/action-types.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/rest-api-filters/data/action-types.js rename to plugins/woocommerce-beta-tester/src/rest-api-filters/data/action-types.js diff --git a/plugins/woocommerce-beta-tester/admin/src/rest-api-filters/data/actions.js b/plugins/woocommerce-beta-tester/src/rest-api-filters/data/actions.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/rest-api-filters/data/actions.js rename to plugins/woocommerce-beta-tester/src/rest-api-filters/data/actions.js diff --git a/plugins/woocommerce-beta-tester/admin/src/rest-api-filters/data/constants.js b/plugins/woocommerce-beta-tester/src/rest-api-filters/data/constants.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/rest-api-filters/data/constants.js rename to plugins/woocommerce-beta-tester/src/rest-api-filters/data/constants.js diff --git a/plugins/woocommerce-beta-tester/admin/src/rest-api-filters/data/index.js b/plugins/woocommerce-beta-tester/src/rest-api-filters/data/index.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/rest-api-filters/data/index.js rename to plugins/woocommerce-beta-tester/src/rest-api-filters/data/index.js diff --git a/plugins/woocommerce-beta-tester/admin/src/rest-api-filters/data/reducer.js b/plugins/woocommerce-beta-tester/src/rest-api-filters/data/reducer.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/rest-api-filters/data/reducer.js rename to plugins/woocommerce-beta-tester/src/rest-api-filters/data/reducer.js diff --git a/plugins/woocommerce-beta-tester/admin/src/rest-api-filters/data/resolvers.js b/plugins/woocommerce-beta-tester/src/rest-api-filters/data/resolvers.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/rest-api-filters/data/resolvers.js rename to plugins/woocommerce-beta-tester/src/rest-api-filters/data/resolvers.js diff --git a/plugins/woocommerce-beta-tester/admin/src/rest-api-filters/data/selectors.js b/plugins/woocommerce-beta-tester/src/rest-api-filters/data/selectors.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/rest-api-filters/data/selectors.js rename to plugins/woocommerce-beta-tester/src/rest-api-filters/data/selectors.js diff --git a/plugins/woocommerce-beta-tester/admin/src/rest-api-filters/index.js b/plugins/woocommerce-beta-tester/src/rest-api-filters/index.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/rest-api-filters/index.js rename to plugins/woocommerce-beta-tester/src/rest-api-filters/index.js diff --git a/plugins/woocommerce-beta-tester/src/src/admin-notes/add-note.js b/plugins/woocommerce-beta-tester/src/src/admin-notes/add-note.js new file mode 100644 index 00000000000..3db4d8eea81 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/admin-notes/add-note.js @@ -0,0 +1,136 @@ +/** + * External dependencies. + */ +import { useState } from '@wordpress/element'; +import { Button, SelectControl } from '@wordpress/components'; +import apiFetch from '@wordpress/api-fetch'; + + +export const AddNote = () => { + const [ isAdding, setIsAdding ] = useState( false ); + const [ hasAdded, setHasAdded ] = useState( false ); + const [ errorMessage, setErrorMessage ] = useState( false ); + const [ noteType, setNoteType ] = useState( 'info' ); + const [ noteLayout, setNoteLayout ] = useState( 'plain' ); + + async function triggerAddNote() { + setIsAdding( true ); + setHasAdded( false ); + setErrorMessage( false ); + + const name = prompt( 'Enter the note name' ); + if ( ! name ) { + setIsAdding( false ); + return; + } + + const title = prompt( 'Enter the note title' ); + if ( ! title ) { + setIsAdding( false ); + return; + } + + try { + await apiFetch( { + path: '/wc-admin-test-helper/admin-notes/add-note/v1', + method: 'POST', + data: { + name, + type: noteType, + layout: noteLayout, + title, + }, + } ); + setHasAdded( true ); + } + catch ( ex ) { + setErrorMessage( ex.message ); + } + + setIsAdding( false ); + } + + function onTypeChange( val ) { + setNoteType( val ); + if ( val !== 'info' ) { + setNoteLayout( 'plain' ); + } + } + + function onLayoutChange( val ) { + setNoteLayout( val ); + } + + function getAddNoteDescription() { + switch ( noteType ){ + case 'email': + return ( + <> + This will add a new email note. Enable email insights{' '} + + here + {' '} + and run the cron to send the note by email. + ); + default: + return ( + <> + This will add a new note. Currently only the note name + and title will be used to create the note. + + ); + } + } + + return ( + <> +

Add a note

+
+ { getAddNoteDescription() } +
+
+ + + +
+
+ + { isAdding && 'Adding, please wait' } + { hasAdded && 'Note added' } + { errorMessage && ( + <> + Error: { errorMessage } + + ) } + +
+ + ); +}; diff --git a/plugins/woocommerce-beta-tester/src/src/admin-notes/admin-notes.js b/plugins/woocommerce-beta-tester/src/src/admin-notes/admin-notes.js new file mode 100644 index 00000000000..5381596a1bb --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/admin-notes/admin-notes.js @@ -0,0 +1,16 @@ +/** + * Internal dependencies. + */ +import { DeleteAllNotes } from './delete-all-notes'; +import { AddNote } from './add-note'; + +export const AdminNotes = () => { + return ( + <> +

Admin notes

+

This section contains tools for managing admin notes.

+ + + + ); +}; diff --git a/plugins/woocommerce-beta-tester/src/src/admin-notes/delete-all-notes.js b/plugins/woocommerce-beta-tester/src/src/admin-notes/delete-all-notes.js new file mode 100644 index 00000000000..3fb75bde464 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/admin-notes/delete-all-notes.js @@ -0,0 +1,69 @@ +/** + * External dependencies. + */ +import { useState } from '@wordpress/element'; +import { Button } from '@wordpress/components'; +import apiFetch from '@wordpress/api-fetch'; + +export const DeleteAllNotes = () => { + const [ isDeleting, setIsDeleting ] = useState( false ); + const [ deleteStatus, setDeleteStatus ] = useState( false ); + const [ errorMessage, setErrorMessage ] = useState( false ); + + async function triggerDeleteAllNotes() { + setIsDeleting( true ); + setErrorMessage( false ); + setDeleteStatus( false ); + + try { + const response = await apiFetch( { + path: '/wc-admin-test-helper/admin-notes/delete-all-notes/v1', + method: 'POST', + } ); + + setDeleteStatus( response ); + } catch ( ex ) { + setErrorMessage( ex.message ); + } + + setIsDeleting( false ); + } + + return ( + <> +

Delete all admin notes

+

+ This will delete all notes from the wp_wc_admin_notes + table, and actions from the wp_wc_admin_note_actions + table. +
+ +
+ + { isDeleting && 'Deleting, please wait.' } + { deleteStatus && ( + <> + Deleted{ ' ' } + { deleteStatus.deleted_note_count }{ ' ' } + admin notes and{ ' ' } + { deleteStatus.deleted_action_count }{ ' ' } + actions. + + ) } + { errorMessage && ( + <> + Error: + { errorMessage } + + ) } + +

+ + ); +}; \ No newline at end of file diff --git a/plugins/woocommerce-beta-tester/src/src/admin-notes/index.js b/plugins/woocommerce-beta-tester/src/src/admin-notes/index.js new file mode 100644 index 00000000000..3e24431f525 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/admin-notes/index.js @@ -0,0 +1 @@ +export { AdminNotes } from './admin-notes.js'; diff --git a/plugins/woocommerce-beta-tester/src/src/app/app.js b/plugins/woocommerce-beta-tester/src/src/app/app.js new file mode 100644 index 00000000000..44e52fe4d3e --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/app/app.js @@ -0,0 +1,72 @@ +/** + * External dependencies + */ +import { TabPanel } from '@wordpress/components'; +import { applyFilters } from '@wordpress/hooks'; + +/** + * Internal dependencies + */ +import { AdminNotes } from '../admin-notes'; +import { default as Tools } from '../tools'; +import { default as Options } from '../options'; +import { default as Experiments } from '../experiments'; +import { default as Features } from '../features'; +import { default as RestAPIFilters } from '../rest-api-filters'; + +const tabs = applyFilters( 'woocommerce_admin_test_helper_tabs', [ + { + name: 'options', + title: 'Options', + content: , + }, + { + name: 'admin-notes', + title: 'Admin notes', + content: , + }, + { + name: 'tools', + title: 'Tools', + content: , + }, + { + name: 'experiments', + title: 'Experiments', + content: , + }, + { + name: 'features', + title: 'Features', + content: , + }, + { + name: 'rest-api-filters', + title: 'REST API FIlters', + content: , + }, +] ); + +export function App() { + return ( +
+

WooCommerce Admin Test Helper

+ + { ( tab ) => ( + <> + { tab.content } + { applyFilters( + `woocommerce_admin_test_helper_tab_${ tab.name }`, + [] + ) } + + ) } + +
+ ); +} diff --git a/plugins/woocommerce-beta-tester/src/src/app/index.js b/plugins/woocommerce-beta-tester/src/src/app/index.js new file mode 100644 index 00000000000..e657810a2c1 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/app/index.js @@ -0,0 +1 @@ +export { App } from './app'; diff --git a/plugins/woocommerce-beta-tester/src/src/experiments/NewExperimentForm.js b/plugins/woocommerce-beta-tester/src/src/experiments/NewExperimentForm.js new file mode 100644 index 00000000000..a42c89a40f1 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/experiments/NewExperimentForm.js @@ -0,0 +1,56 @@ +/** + * External dependencies + */ +import { withDispatch } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; +import { Button } from '@wordpress/components'; +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { STORE_KEY } from './data/constants'; +import './data'; + +function NewExperimentForm( { addExperiment } ) { + const [ experimentName, setExperimentName ] = useState( null ); + const [ variation, setVariation ] = useState( 'treatment' ); + + const getInputValue = ( event ) => { + setExperimentName( event.target.value ); + }; + + const getVariationInput = ( event ) => { + setVariation( event.target.value ); + }; + + const AddNewExperiment = () => { + addExperiment( experimentName, variation ); + }; + + return ( +
+
+ Don't see an experiment you want to test? Add it manually. +
+ + + + +
+ ); +} + +export default compose( + withDispatch( ( dispatch ) => { + const { addExperiment } = dispatch( STORE_KEY ); + return { + addExperiment, + }; + } ) +)( NewExperimentForm ); diff --git a/plugins/woocommerce-beta-tester/src/src/experiments/data/action-types.js b/plugins/woocommerce-beta-tester/src/src/experiments/data/action-types.js new file mode 100644 index 00000000000..3402b35dc10 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/experiments/data/action-types.js @@ -0,0 +1,8 @@ +const TYPES = { + TOGGLE_EXPERIMENT: 'TOGGLE_EXPERIMENT', + SET_EXPERIMENTS: 'SET_EXPERIMENTS', + ADD_EXPERIMENT: 'ADD_EXPERIMENT', + DELETE_EXPERIMENT: 'DELETE_EXPERIMENT', +}; + +export default TYPES; diff --git a/plugins/woocommerce-beta-tester/src/src/experiments/data/actions.js b/plugins/woocommerce-beta-tester/src/src/experiments/data/actions.js new file mode 100644 index 00000000000..8ac78e3d40a --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/experiments/data/actions.js @@ -0,0 +1,105 @@ +/** + * External dependencies + */ +import { apiFetch } from '@wordpress/data-controls'; + +/** + * Internal dependencies + */ +import TYPES from './action-types'; +import { + EXPERIMENT_NAME_PREFIX, + TRANSIENT_NAME_PREFIX, + TRANSIENT_TIMEOUT_NAME_PREFIX, +} from './constants'; + +function toggleFrontendExperiment( experimentName, newVariation ) { + let storageItem = JSON.parse( + window.localStorage.getItem( EXPERIMENT_NAME_PREFIX + experimentName ) + ); + + // If the experiment is not in localStorage, consider it as a new. + if ( storageItem === null ) { + storageItem = { + experimentName, + retrievedTimestamp: Date.now(), + }; + } + + storageItem.variationName = newVariation; + storageItem.ttl = 3600; + + window.localStorage.setItem( + EXPERIMENT_NAME_PREFIX + experimentName, + JSON.stringify( storageItem ) + ); +} + +function* toggleBackendExperiment( experimentName, newVariation ) { + try { + const payload = {}; + payload[ TRANSIENT_NAME_PREFIX + experimentName ] = newVariation; + payload[ TRANSIENT_TIMEOUT_NAME_PREFIX + experimentName ] = + Math.round( Date.now() / 1000 ) + 3600; + + yield apiFetch( { + method: 'POST', + path: '/wc-admin/options', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify( payload ), + } ); + } catch ( error ) { + throw new Error(); + } +} + +export function* toggleExperiment( experimentName, currentVariation ) { + const newVariation = + currentVariation === 'control' ? 'treatment' : 'control'; + + toggleFrontendExperiment( experimentName, newVariation ); + yield toggleBackendExperiment( experimentName, newVariation ); + + return { + type: TYPES.TOGGLE_EXPERIMENT, + experimentName, + newVariation, + }; +} + +export function setExperiments( experiments ) { + return { + type: TYPES.SET_EXPERIMENTS, + experiments, + }; +} + +export function* addExperiment( experimentName, variation ) { + toggleFrontendExperiment( experimentName, variation ); + yield toggleBackendExperiment( experimentName, variation ); + + return { + type: TYPES.ADD_EXPERIMENT, + experimentName, + variation, + }; +} + +export function* deleteExperiment( experimentName ) { + window.localStorage.removeItem( EXPERIMENT_NAME_PREFIX + experimentName ); + + const optionNames = [ + TRANSIENT_NAME_PREFIX + experimentName, + TRANSIENT_TIMEOUT_NAME_PREFIX + experimentName, + ]; + + yield apiFetch( { + method: 'DELETE', + path: '/wc-admin-test-helper/options/' + optionNames.join( ',' ), + } ); + + return { + type: TYPES.DELETE_EXPERIMENT, + experimentName, + }; +} diff --git a/plugins/woocommerce-beta-tester/src/src/experiments/data/constants.js b/plugins/woocommerce-beta-tester/src/src/experiments/data/constants.js new file mode 100644 index 00000000000..31f2b8f97e6 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/experiments/data/constants.js @@ -0,0 +1,6 @@ +export const STORE_KEY = 'wc-admin-helper/experiments'; +export const EXPERIMENT_NAME_PREFIX = 'explat-experiment--'; +export const TRANSIENT_NAME_PREFIX = '_transient_abtest_variation_'; +export const TRANSIENT_TIMEOUT_NAME_PREFIX = + '_transient_timeout_abtest_variation'; +export const API_NAMESPACE = '/wc-admin-test-helper'; diff --git a/plugins/woocommerce-beta-tester/admin/src/tools/data/index.js b/plugins/woocommerce-beta-tester/src/src/experiments/data/index.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/tools/data/index.js rename to plugins/woocommerce-beta-tester/src/src/experiments/data/index.js diff --git a/plugins/woocommerce-beta-tester/src/src/experiments/data/reducer.js b/plugins/woocommerce-beta-tester/src/src/experiments/data/reducer.js new file mode 100644 index 00000000000..3f34bd0a8ef --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/experiments/data/reducer.js @@ -0,0 +1,72 @@ +/** + * Internal dependencies + */ +import TYPES from './action-types'; + +const DEFAULT_STATE = { + experiments: [], +}; + +const reducer = ( state = DEFAULT_STATE, action ) => { + switch ( action.type ) { + case TYPES.DELETE_EXPERIMENT: + return { + ...state, + experiments: state.experiments.filter( ( experiment ) => { + return experiment.name !== action.experimentName; + } ), + }; + case TYPES.ADD_EXPERIMENT: + const existingExperimentIndex = state.experiments.findIndex( + ( element ) => { + return element.name === action.experimentName; + } + ); + const newExperiment = { + name: action.experimentName, + variation: action.variation, + }; + const newExperiments = + existingExperimentIndex !== -1 + ? state.experiments + .slice( 0, existingExperimentIndex ) + .concat( newExperiment ) + .concat( + state.experiments.slice( + existingExperimentIndex + 1 + ) + ) + : [ + ...state.experiments, + { + name: action.experimentName, + variation: action.variation, + }, + ]; + + return { + ...state, + experiments: newExperiments, + }; + case TYPES.TOGGLE_EXPERIMENT: + return { + ...state, + experiments: state.experiments.map( ( experiment ) => ( { + ...experiment, + variation: + experiment.name === action.experimentName + ? action.newVariation + : experiment.variation, + } ) ), + }; + case TYPES.SET_EXPERIMENTS: + return { + ...state, + experiments: action.experiments, + }; + default: + return state; + } +}; + +export default reducer; diff --git a/plugins/woocommerce-beta-tester/src/src/experiments/data/resolvers.js b/plugins/woocommerce-beta-tester/src/src/experiments/data/resolvers.js new file mode 100644 index 00000000000..c5a740b6760 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/experiments/data/resolvers.js @@ -0,0 +1,68 @@ +/** + * External dependencies + */ +import { apiFetch } from '@wordpress/data-controls'; + +/** + * Internal dependencies + */ +import { setExperiments } from './actions'; +import { + EXPERIMENT_NAME_PREFIX, + TRANSIENT_NAME_PREFIX, + API_NAMESPACE, +} from './constants'; + +function getExperimentsFromFrontend() { + const storageItems = Object.entries( { ...window.localStorage } ).filter( + ( item ) => { + return item[ 0 ].indexOf( EXPERIMENT_NAME_PREFIX ) === 0; + } + ); + + return storageItems.map( ( storageItem ) => { + const [ key, value ] = storageItem; + const objectValue = JSON.parse( value ); + return { + name: key.replace( EXPERIMENT_NAME_PREFIX, '' ), + variation: objectValue.variationName || 'control', + }; + } ); +} + +export function* getExperiments() { + try { + const response = yield apiFetch( { + path: `${ API_NAMESPACE }/options?search=_transient_abtest_variation_`, + } ); + + const experimentsFromBackend = response.map( ( experiment ) => { + return { + name: experiment.option_name.replace( + TRANSIENT_NAME_PREFIX, + '' + ), + variation: + experiment.option_value === 'control' + ? 'control' + : 'treatment', + }; + } ); + + // Remove duplicate. + const experiments = getExperimentsFromFrontend() + .concat( experimentsFromBackend ) + .filter( + ( value, index, self ) => + index === + self.findIndex( + ( t ) => + t.place === value.place && t.name === value.name + ) + ); + + yield setExperiments( experiments ); + } catch ( error ) { + throw new Error(); + } +} diff --git a/plugins/woocommerce-beta-tester/src/src/experiments/data/selectors.js b/plugins/woocommerce-beta-tester/src/src/experiments/data/selectors.js new file mode 100644 index 00000000000..f988d197e32 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/experiments/data/selectors.js @@ -0,0 +1,3 @@ +export function getExperiments( state ) { + return state.experiments; +} diff --git a/plugins/woocommerce-beta-tester/src/src/experiments/index.js b/plugins/woocommerce-beta-tester/src/src/experiments/index.js new file mode 100644 index 00000000000..10c364173f0 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/experiments/index.js @@ -0,0 +1,110 @@ +/** + * External dependencies + */ +import { withDispatch, withSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; +import { Button } from '@wordpress/components'; +import { OPTIONS_STORE_NAME } from '@woocommerce/data'; +/** + * Internal dependencies + */ +import { STORE_KEY } from './data/constants'; +import './data'; +import NewExperimentForm from './NewExperimentForm'; + +function Experiments( { + experiments, + toggleExperiment, + deleteExperiment, + isTrackingEnabled, + isResolving, +} ) { + if ( isResolving ) { + return null; + } + + return ( +
+

Experiments

+ { isTrackingEnabled === 'no' && ( +

+ The following list might not be complete without tracking + enabled.
+ Please visit  + + WooCommerce → Settings → Advanced → + Woocommerce.com + +  and check{ ' ' } + Allow usage of WooCommerce to be tracked. +

+ ) } + + + + + + + + + + + { experiments.map( ( { name, variation }, index ) => { + return ( + + + + + + ); + } ) } + +
ExperimentVariationActions
{ name }{ variation } + + +
+
+ ); +} + +export default compose( + withSelect( ( select ) => { + const { getExperiments } = select( STORE_KEY ); + const { getOption, isResolving } = select( OPTIONS_STORE_NAME ); + + return { + experiments: getExperiments(), + isTrackingEnabled: getOption( 'woocommerce_allow_tracking' ), + isResolving: isResolving( 'getOption', [ 'getExperiments' ] ), + }; + } ), + withDispatch( ( dispatch ) => { + const { toggleExperiment, deleteExperiment } = dispatch( STORE_KEY ); + + return { + toggleExperiment, + deleteExperiment, + }; + } ) +)( Experiments ); diff --git a/plugins/woocommerce-beta-tester/src/src/features/data/action-types.js b/plugins/woocommerce-beta-tester/src/src/features/data/action-types.js new file mode 100644 index 00000000000..1e6a4d8a3bf --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/features/data/action-types.js @@ -0,0 +1,7 @@ +const TYPES = { + TOGGLE_FEATURE: 'TOGGLE_FEATURE', + SET_FEATURES: 'SET_FEATURES', + SET_MODIFIED_FEATURES: 'SET_MODIFIED_FEATURES', +}; + +export default TYPES; diff --git a/plugins/woocommerce-beta-tester/src/src/features/data/actions.js b/plugins/woocommerce-beta-tester/src/src/features/data/actions.js new file mode 100644 index 00000000000..25bc100d791 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/features/data/actions.js @@ -0,0 +1,57 @@ +/** + * External dependencies + */ +import { apiFetch } from '@wordpress/data-controls'; +import { controls } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import TYPES from './action-types'; +import { API_NAMESPACE, STORE_KEY } from './constants'; + +export function* resetModifiedFeatures() { + try { + const response = yield apiFetch( { + path: `${ API_NAMESPACE }/features/reset`, + method: 'POST', + } ); + + yield setModifiedFeatures( [] ); + yield setFeatures( response ); + } catch ( error ) { + throw new Error(); + } +} + +export function* toggleFeature( featureName ) { + try { + const response = yield apiFetch( { + method: 'POST', + path: API_NAMESPACE + '/features/' + featureName + '/toggle', + headers: { 'content-type': 'application/json' }, + } ); + yield setFeatures( response ); + yield controls.dispatch( + STORE_KEY, + 'invalidateResolutionForStoreSelector', + 'getModifiedFeatures' + ); + } catch ( error ) { + throw new Error(); + } +} + +export function setFeatures( features ) { + return { + type: TYPES.SET_FEATURES, + features, + }; +} + +export function setModifiedFeatures( modifiedFeatures ) { + return { + type: TYPES.SET_MODIFIED_FEATURES, + modifiedFeatures, + }; +} diff --git a/plugins/woocommerce-beta-tester/src/src/features/data/constants.js b/plugins/woocommerce-beta-tester/src/src/features/data/constants.js new file mode 100644 index 00000000000..c9da14951e5 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/features/data/constants.js @@ -0,0 +1,3 @@ +export const STORE_KEY = 'wc-admin-helper/features'; +export const OPTION_NAME_PREFIX = 'wc_admin_helper_feature_values'; +export const API_NAMESPACE = '/wc-admin-test-helper'; diff --git a/plugins/woocommerce-beta-tester/src/src/features/data/index.js b/plugins/woocommerce-beta-tester/src/src/features/data/index.js new file mode 100644 index 00000000000..e476479ea88 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/features/data/index.js @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import { registerStore } from '@wordpress/data'; +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'; + +export default registerStore( STORE_KEY, { + actions, + selectors, + resolvers, + controls, + reducer, +} ); diff --git a/plugins/woocommerce-beta-tester/src/src/features/data/reducer.js b/plugins/woocommerce-beta-tester/src/src/features/data/reducer.js new file mode 100644 index 00000000000..61999300c24 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/features/data/reducer.js @@ -0,0 +1,28 @@ +/** + * Internal dependencies + */ +import TYPES from './action-types'; + +const DEFAULT_STATE = { + features: {}, + modifiedFeatures: [], +}; + +const reducer = ( state = DEFAULT_STATE, action ) => { + switch ( action.type ) { + case TYPES.SET_MODIFIED_FEATURES: + return { + ...state, + modifiedFeatures: action.modifiedFeatures, + }; + case TYPES.SET_FEATURES: + return { + ...state, + features: action.features, + }; + default: + return state; + } +}; + +export default reducer; diff --git a/plugins/woocommerce-beta-tester/src/src/features/data/resolvers.js b/plugins/woocommerce-beta-tester/src/src/features/data/resolvers.js new file mode 100644 index 00000000000..29d7ccfbf19 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/features/data/resolvers.js @@ -0,0 +1,38 @@ +/** + * External dependencies + */ +import { apiFetch } from '@wordpress/data-controls'; + +/** + * Internal dependencies + */ +import { setFeatures, setModifiedFeatures } from './actions'; +import { API_NAMESPACE, OPTION_NAME_PREFIX } from './constants'; + +export function* getModifiedFeatures() { + try { + const response = yield apiFetch( { + path: `wc-admin/options?options=` + OPTION_NAME_PREFIX, + } ); + + yield setModifiedFeatures( + response && response[ OPTION_NAME_PREFIX ] + ? Object.keys( response[ OPTION_NAME_PREFIX ] ) + : [] + ); + } catch ( error ) { + throw new Error(); + } +} + +export function* getFeatures() { + try { + const response = yield apiFetch( { + path: `${ API_NAMESPACE }/features`, + } ); + + yield setFeatures( response ); + } catch ( error ) { + throw new Error(); + } +} diff --git a/plugins/woocommerce-beta-tester/src/src/features/data/selectors.js b/plugins/woocommerce-beta-tester/src/src/features/data/selectors.js new file mode 100644 index 00000000000..6fa3938dafb --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/features/data/selectors.js @@ -0,0 +1,7 @@ +export function getFeatures( state ) { + return state.features; +} + +export function getModifiedFeatures( state ) { + return state.modifiedFeatures; +} diff --git a/plugins/woocommerce-beta-tester/src/src/features/index.js b/plugins/woocommerce-beta-tester/src/src/features/index.js new file mode 100644 index 00000000000..f89defecc1c --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/features/index.js @@ -0,0 +1,72 @@ +/** + * External dependencies + */ +import { useDispatch, useSelect } from '@wordpress/data'; +import { Button } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { STORE_KEY } from './data/constants'; +import './data'; + +function Features() { + const { features = {}, modifiedFeatures = [] } = useSelect( ( select ) => { + const { getFeatures, getModifiedFeatures } = select( STORE_KEY ); + return { + features: getFeatures(), + modifiedFeatures: getModifiedFeatures(), + }; + } ); + + const { toggleFeature, resetModifiedFeatures } = useDispatch( STORE_KEY ); + + return ( +
+

+ Features + +

+ + + + + + + + + + { Object.keys( features ).map( ( feature_name ) => { + return ( + + + + + + ); + } ) } + +
Feature NameEnabled?Toggle
+ { feature_name } + { features[ feature_name ].toString() } + +
+
+ ); +} + +export default Features; diff --git a/plugins/woocommerce-beta-tester/src/src/index.js b/plugins/woocommerce-beta-tester/src/src/index.js new file mode 100644 index 00000000000..56be948f2cb --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/index.js @@ -0,0 +1,18 @@ +/** + * External dependencies + */ +import { render } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { App } from './app'; +import './index.scss'; + +const appRoot = document.getElementById( + 'woocommerce-admin-test-helper-app-root' +); + +if (appRoot) { + render(, appRoot); +} diff --git a/plugins/woocommerce-beta-tester/src/src/index.scss b/plugins/woocommerce-beta-tester/src/src/index.scss new file mode 100644 index 00000000000..b051363c9f4 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/index.scss @@ -0,0 +1,155 @@ +#woocommerce-admin-test-helper-app-root { + .btn-danger { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; + } + + .btn-primary { + color: #fff; + background-color: #007bff; + border-color: #007bff; + } +} + +.woocommerce-admin-test-helper__main-tab-panel { + .active-tab { + box-shadow: inset 0 1.5px #007cba; + box-shadow: inset 0 1.5px var( --wp-admin-theme-color ); + } +} + +.woocommerce-admin-test-helper__action-status { + color: #007cba; + color: var( --wp-admin-theme-color ); + font-family: monospace; +} + +.woocommerce-admin-test-helper__add-notes { + width: 410px; + display: flex; + justify-content: space-between; + .components-base-control__field { + margin-bottom: 0; + padding-top: 3px; + } +} + +#wc-admin-test-helper-options { + div.search-box { + float: right; + margin-bottom: 10px; + } + + .align-center { + text-align: center; + } + + .components-notice { + margin: 0px 0px 10px 0px; + } +} + +.wca-test-helper-option-editor { + width: 100%; + height: 300px; +} + +.wca-test-helper-edit-btn-save { + float: right; +} + +#wc-admin-test-helper-tools, +#wc-admin-test-helper-experiments { + table.tools, + table.experiments { + thead th { + text-align: center; + } + tbody td { + vertical-align: middle; + &.command { + white-space: nowrap; + } + .trigger-cron-job { + width: 40%; + padding-top: 4px; + .components-base-control__field { + margin-bottom: 0; + } + } + } + } + .components-notice { + margin: 0px 0px 10px 0px; + } + .tracking-disabled { + border: 1px solid #cc99c2; + border-left: 4px solid #cc99c2; + line-height: 1.5em; + background: #fff; + padding: 10px; + } +} + +#wc-admin-test-helper-experiments { + .actions { + button { + margin-right: 5px; + } + } + .manual-input { + margin-bottom: 20px; + float: right; + .description { + text-align: right; + margin-bottom: 5px; + } + button { + height: 34px; + position: relative; + top: -1px; + } + input { + height: 34px; + width: 250px; + } + select { + height: 34px; + position: relative; + top: -2px; + margin-right: 2px; + } + } +} + +#wc-admin-test-helper-rest-api-filters { + .btn-new { + float: right; + } +} + +form.rest-api-filter-new-form { + .grid { + display: grid; + grid-template-columns: max-content max-content; + grid-gap: 5px; + input[type='text'] { + width: 350px; + } + label { + text-align: right; + } + label:after { + content: ':'; + } + } + + .btn-new { + color: #fff; + background-color: #007bff; + border-color: #007bff; + float: right; + margin-top: 10px; + } +} diff --git a/plugins/woocommerce-beta-tester/src/src/options/OptionEditor.js b/plugins/woocommerce-beta-tester/src/src/options/OptionEditor.js new file mode 100644 index 00000000000..5784b179fd3 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/options/OptionEditor.js @@ -0,0 +1,48 @@ +/** + * External dependencies + */ +import { useEffect, useState } from '@wordpress/element'; +import PropTypes from 'prop-types'; +import { Button } from '@wordpress/components'; + +const OptionEditor = ( props ) => { + const [ value, setValue ] = useState( props.option.content ); + + useEffect( () => { + setValue( props.option.content ); + }, [ props.option ] ); + + const handleChange = ( event ) => { + setValue( event.target.value ); + }; + + const handleSave = () => { + props.onSave( props.option.name, value ); + }; + + return ( + <> + + +
+ + ); +}; + +OptionEditor.propTypes = { + option: PropTypes.object.isRequired, + onSave: PropTypes.func.isRequired, +}; + +export default OptionEditor; diff --git a/plugins/woocommerce-beta-tester/src/src/options/data/action-types.js b/plugins/woocommerce-beta-tester/src/src/options/data/action-types.js new file mode 100644 index 00000000000..661bfcbe3bf --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/options/data/action-types.js @@ -0,0 +1,9 @@ +const TYPES = { + SET_OPTIONS: 'SET_OPTIONS', + SET_OPTION_FOR_EDITING: 'SET_OPTION_FOR_EDITING', + SET_IS_LOADING: 'SET_IS_LOADING', + SET_NOTICE: 'SET_NOTICE', + DELETE_OPTION: 'DELETE_OPTION', +}; + +export default TYPES; diff --git a/plugins/woocommerce-beta-tester/src/src/options/data/actions.js b/plugins/woocommerce-beta-tester/src/src/options/data/actions.js new file mode 100644 index 00000000000..1f3398281f3 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/options/data/actions.js @@ -0,0 +1,81 @@ +/** + * External dependencies + */ +import { apiFetch } from '@wordpress/data-controls'; + +/** + * Internal dependencies + */ +import TYPES from './action-types'; +import { API_NAMESPACE } from './constants'; + +/** + * Initialize the state + * + * @param {Array} options + */ +export function setOptions( options ) { + return { + type: TYPES.SET_OPTIONS, + options, + }; +} + +export function setLoadingState( isLoading ) { + return { + type: TYPES.SET_IS_LOADING, + isLoading, + }; +} + +export function setOptionForEditing( editingOption ) { + return { + type: TYPES.SET_OPTION_FOR_EDITING, + editingOption, + }; +} + +export function setNotice( notice ) { + return { + type: TYPES.SET_NOTICE, + notice, + }; +} + +export function* deleteOption( optionName ) { + try { + yield apiFetch( { + method: 'DELETE', + path: `${ API_NAMESPACE }/options/${ optionName }`, + } ); + yield { + type: TYPES.DELETE_OPTION, + optionName, + }; + } catch { + throw new Error(); + } +} + +export function* saveOption( optionName, newOptionValue ) { + try { + const payload = {}; + payload[ optionName ] = JSON.parse( newOptionValue ); + yield apiFetch( { + method: 'POST', + path: '/wc-admin/options', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify( payload ), + } ); + yield setNotice( { + status: 'success', + message: optionName + ' has been saved.', + } ); + } catch { + yield setNotice( { + status: 'error', + message: 'Unable to save ' + optionName, + } ); + throw new Error(); + } +} diff --git a/plugins/woocommerce-beta-tester/src/src/options/data/constants.js b/plugins/woocommerce-beta-tester/src/src/options/data/constants.js new file mode 100644 index 00000000000..fbd7aaf4142 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/options/data/constants.js @@ -0,0 +1,2 @@ +export const STORE_KEY = 'wc-admin-helper/options'; +export const API_NAMESPACE = '/wc-admin-test-helper'; diff --git a/plugins/woocommerce-beta-tester/src/src/options/data/index.js b/plugins/woocommerce-beta-tester/src/src/options/data/index.js new file mode 100644 index 00000000000..e476479ea88 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/options/data/index.js @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import { registerStore } from '@wordpress/data'; +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'; + +export default registerStore( STORE_KEY, { + actions, + selectors, + resolvers, + controls, + reducer, +} ); diff --git a/plugins/woocommerce-beta-tester/src/src/options/data/reducer.js b/plugins/woocommerce-beta-tester/src/src/options/data/reducer.js new file mode 100644 index 00000000000..7b400a5810b --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/options/data/reducer.js @@ -0,0 +1,60 @@ +/** + * Internal dependencies + */ +import TYPES from './action-types'; + +const DEFAULT_STATE = { + options: [], + isLoading: true, + editingOption: { + name: null, + content: '{}', + }, + notice: { + status: 'success', + message: '', + }, +}; + +const reducer = ( state = DEFAULT_STATE, action ) => { + switch ( action.type ) { + case TYPES.SET_OPTION_FOR_EDITING: + return { + ...state, + editingOption: { + ...state.editingOption, + ...action.editingOption, + }, + }; + case TYPES.SET_IS_LOADING: + return { + ...state, + isLoading: action.isLoading, + }; + case TYPES.SET_OPTIONS: + return { + ...state, + options: action.options, + isLoading: false, + }; + case TYPES.SET_NOTICE: + return { + ...state, + notice: { + ...state.notice, + ...action.notice, + }, + }; + case TYPES.DELETE_OPTION: + return { + ...state, + options: state.options.filter( + ( item ) => item.option_name !== action.optionName + ), + }; + default: + return state; + } +}; + +export default reducer; diff --git a/plugins/woocommerce-beta-tester/src/src/options/data/resolvers.js b/plugins/woocommerce-beta-tester/src/src/options/data/resolvers.js new file mode 100644 index 00000000000..6c1ec7ce2ad --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/options/data/resolvers.js @@ -0,0 +1,61 @@ +/** + * External dependencies + */ +import { apiFetch } from '@wordpress/data-controls'; + +/** + * Internal dependencies + */ +import { API_NAMESPACE } from './constants'; +import { setLoadingState, setOptions, setOptionForEditing } from './actions'; + +export function* getOptions( search ) { + let path = `${ API_NAMESPACE }/options?`; + if ( search ) { + path += `search=${ search }`; + } + + yield setLoadingState( true ); + + try { + const response = yield apiFetch( { + path, + } ); + yield setOptions( response ); + } catch ( error ) { + throw new Error(); + } +} + +export function* getOptionForEditing( optionName ) { + const loadingOption = { + name: 'Loading...', + content: '', + saved: false, + }; + if ( optionName === undefined ) { + return setOptionForEditing( loadingOption ); + } + + yield setOptionForEditing( loadingOption ); + + const path = '/wc-admin/options?options=' + optionName; + + try { + const response = yield apiFetch( { + path, + } ); + + let content = response[ optionName ]; + if ( typeof content === 'object' ) { + content = JSON.stringify( response[ optionName ], null, 2 ); + } + + yield setOptionForEditing( { + name: optionName, + content, + } ); + } catch ( error ) { + throw new Error( error ); + } +} diff --git a/plugins/woocommerce-beta-tester/src/src/options/data/selectors.js b/plugins/woocommerce-beta-tester/src/src/options/data/selectors.js new file mode 100644 index 00000000000..c2b78240e34 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/options/data/selectors.js @@ -0,0 +1,15 @@ +export function getOptions( state ) { + return state.options; +} + +export function isLoading( state ) { + return state.isLoading; +} + +export function getOptionForEditing( state ) { + return state.editingOption; +} + +export function getNotice( state ) { + return state.notice; +} diff --git a/plugins/woocommerce-beta-tester/src/src/options/index.js b/plugins/woocommerce-beta-tester/src/src/options/index.js new file mode 100644 index 00000000000..a85a37afcea --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/options/index.js @@ -0,0 +1,257 @@ +/** + * External dependencies + */ +import { withDispatch, withSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; +import { Modal, Notice } from '@wordpress/components'; +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { STORE_KEY } from './data/constants'; +import { default as OptionEditor } from './OptionEditor'; +import './data'; + +function shorten( input ) { + if ( input.length > 20 ) { + return input.substring( 0, 20 ) + '...'; + } + + return input; +} + +function Options( { + options, + getOptions, + deleteOption, + isLoading, + invalidateResolution, + getOptionForEditing, + editingOption, + saveOption, + notice, + setNotice, +} ) { + const [ isEditModalOpen, setEditModalOpen ] = useState( false ); + + const deleteOptionByName = ( optionName ) => { + // eslint-disable-next-line no-alert + if ( confirm( 'Are you sure you want to delete this option?' ) ) { + deleteOption( optionName ); + } + }; + + const openEditModal = ( optionName ) => { + invalidateResolution( STORE_KEY, 'getOptionForEditing', [ + optionName, + ] ); + + getOptionForEditing( optionName ); + setEditModalOpen( true ); + }; + + const handleSaveOption = ( optionName, newValue ) => { + saveOption( optionName, newValue ); + setEditModalOpen( false ); + }; + + const renderLoading = () => { + return ( + + + Loading... + + + ); + }; + + const renderTableData = () => { + if ( options.length === 0 ) { + return ( + + + No Options Found + + + ); + } + + return options.map( ( option ) => { + // eslint-disable-next-line camelcase + const { option_id, option_name, option_value, autoload } = option; + + // eslint-disable-next-line camelcase + const optionId = option_id; + // eslint-disable-next-line camelcase + const optionName = option_name; + // eslint-disable-next-line camelcase + const optionValue = shorten( option_value ); + + return ( + + { optionId } + { optionName } + { optionValue } + + { autoload } + + + + + + + + + ); + } ); + }; + + const searchOption = ( event ) => { + event.preventDefault(); + const keyword = event.target.search.value; + + // Invalidate resolution of the same selector + arg + // so that entering the same keyword always works + invalidateResolution( STORE_KEY, 'getOptions', [ keyword ] ); + + getOptions( keyword ); + }; + + return ( + <> + { isEditModalOpen && ( + { + setEditModalOpen( false ); + } } + > + + + ) } +
+ { notice.message.length > 0 && ( + { + setNotice( { message: '' } ); + } } + > + { notice.message } + + ) } +
+
+ + + +
+
+
+ + + + + + + + + + + + + { isLoading ? renderLoading() : renderTableData() } + +
+ I.D + + Name + + Value + + Autoload + + Delete + + Edit +
+
+ + ); +} + +export default compose( + withSelect( ( select ) => { + const { + getOptions, + getOptionForEditing, + getNotice, + isLoading, + } = select( STORE_KEY ); + const options = getOptions(); + const editingOption = getOptionForEditing(); + const notice = getNotice(); + + return { + options, + getOptions, + isLoading: isLoading(), + editingOption, + getOptionForEditing, + notice, + }; + } ), + withDispatch( ( dispatch ) => { + const { deleteOption, saveOption, setNotice } = dispatch( STORE_KEY ); + const { invalidateResolution } = dispatch( 'core/data' ); + + return { + deleteOption, + invalidateResolution, + saveOption, + setNotice, + }; + } ) +)( Options ); diff --git a/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/action-types.js b/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/action-types.js new file mode 100644 index 00000000000..4fd3c25daf5 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/action-types.js @@ -0,0 +1,9 @@ +const TYPES = { + SET_FILTERS: 'SET_FILTERS', + SET_IS_LOADING: 'SET_IS_LOADING', + DELETE_FILTER: 'DELETE_FILTER', + SAVE_FILTER: 'SAVE_FILTER', + TOGGLE_FILTER: 'TOGGLE_FILTER', +}; + +export default TYPES; diff --git a/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/actions.js b/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/actions.js new file mode 100644 index 00000000000..09f2d8fc604 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/actions.js @@ -0,0 +1,93 @@ +/** + * External dependencies + */ +import { apiFetch } from '@wordpress/data-controls'; + +/** + * Internal dependencies + */ +import TYPES from './action-types'; +import { API_NAMESPACE } from './constants'; + +/** + * Initialize the state + * + * @param {Array} filter + * @param filters + */ +export function setFilters( filters ) { + return { + type: TYPES.SET_FILTERS, + filters, + }; +} + +export function setLoadingState( isLoading ) { + return { + type: TYPES.SET_IS_LOADING, + isLoading, + }; +} + +export function* toggleFilter( index ) { + try { + yield apiFetch( { + method: 'POST', + path: `${ API_NAMESPACE }/rest-api-filters/${ index }/toggle`, + headers: { 'content-type': 'application/json' }, + } ); + yield { + type: TYPES.TOGGLE_FILTER, + index, + }; + } catch { + throw new Error(); + } +} + +export function* deleteFilter( index ) { + try { + yield apiFetch( { + method: 'DELETE', + path: `${ API_NAMESPACE }/rest-api-filters/`, + headers: { 'content-type': 'application/json' }, + body: JSON.stringify( { + index, + } ), + } ); + + yield { + type: TYPES.DELETE_FILTER, + index, + }; + } catch { + throw new Error(); + } +} + +export function* saveFilter( endpoint, dotNotation, replacement ) { + try { + yield apiFetch( { + method: 'POST', + path: API_NAMESPACE + '/rest-api-filters', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify( { + endpoint, + dot_notation: dotNotation, + replacement, + } ), + } ); + + yield { + type: TYPES.SAVE_FILTER, + filter: { + endpoint, + dot_notation: dotNotation, + replacement, + enabled: true, + }, + }; + } catch { + throw new Error(); + } +} diff --git a/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/constants.js b/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/constants.js new file mode 100644 index 00000000000..a8375a21498 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/constants.js @@ -0,0 +1,5 @@ +export const STORE_KEY = 'wc-admin-helper/rest-api-filters'; +export const API_NAMESPACE = '/wc-admin-test-helper'; + +// Option name where we're going to save the filters. +export const FILTERS_OPTION_NAME = 'wc-admin-test-helper-rest-api-filters'; diff --git a/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/index.js b/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/index.js new file mode 100644 index 00000000000..e476479ea88 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/index.js @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import { registerStore } from '@wordpress/data'; +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'; + +export default registerStore( STORE_KEY, { + actions, + selectors, + resolvers, + controls, + reducer, +} ); diff --git a/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/reducer.js b/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/reducer.js new file mode 100644 index 00000000000..3a906c78e49 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/reducer.js @@ -0,0 +1,55 @@ +/** + * Internal dependencies + */ +import TYPES from './action-types'; + +const DEFAULT_STATE = { + filters: [], + isLoading: true, + notice: { + status: 'success', + message: '', + }, +}; + +const reducer = ( state = DEFAULT_STATE, action ) => { + switch ( action.type ) { + case TYPES.TOGGLE_FILTER: + return { + ...state, + filters: state.filters.map( ( filter, index ) => { + if ( index === action.index ) { + filter.enabled = ! filter.enabled; + } + return filter; + } ), + }; + case TYPES.SET_IS_LOADING: + return { + ...state, + isLoading: action.isLoading, + }; + case TYPES.SET_FILTERS: + return { + ...state, + filters: action.filters, + isLoading: false, + }; + case TYPES.DELETE_FILTER: + return { + ...state, + filters: state.filters.filter( + ( item, index ) => index !== action.index + ), + }; + case TYPES.SAVE_FILTER: + return { + ...state, + filters: [ ...state.filters, action.filter ], + }; + default: + return state; + } +}; + +export default reducer; diff --git a/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/resolvers.js b/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/resolvers.js new file mode 100644 index 00000000000..0b88740a4cf --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/resolvers.js @@ -0,0 +1,29 @@ +/** + * External dependencies + */ +import { apiFetch } from '@wordpress/data-controls'; + +/** + * Internal dependencies + */ +import { FILTERS_OPTION_NAME } from './constants'; +import { setLoadingState, setFilters } from './actions'; + +export function* getFilters() { + const path = '/wc-admin/options?options=' + FILTERS_OPTION_NAME; + + yield setLoadingState( true ); + + try { + const response = yield apiFetch( { + path, + } ); + if ( response[ FILTERS_OPTION_NAME ] === false ) { + yield setFilters( [] ); + } else { + yield setFilters( response[ FILTERS_OPTION_NAME ] ); + } + } catch ( error ) { + throw new Error(); + } +} diff --git a/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/selectors.js b/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/selectors.js new file mode 100644 index 00000000000..44ca07aa452 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/rest-api-filters/data/selectors.js @@ -0,0 +1,7 @@ +export function getFilters( state ) { + return state.filters; +} + +export function isLoading( state ) { + return state.isLoading; +} diff --git a/plugins/woocommerce-beta-tester/src/src/rest-api-filters/index.js b/plugins/woocommerce-beta-tester/src/src/rest-api-filters/index.js new file mode 100644 index 00000000000..821da5869fc --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/rest-api-filters/index.js @@ -0,0 +1,182 @@ +/** + * External dependencies + */ +import { withDispatch, withSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; +import { Modal } from '@wordpress/components'; +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { STORE_KEY } from './data/constants'; +import './data'; + +function RestAPIFilters( { + filters, + deleteFilter, + isLoading, + saveFilter, + toggleFilter, +} ) { + const [ isNewModalOpen, setNewModalOpen ] = useState( false ); + + const submitAddForm = ( e ) => { + e.preventDefault(); + saveFilter( + e.target.endpoint.value, + e.target.dotNotation.value, + e.target.replacement.value + ); + setNewModalOpen( false ); + }; + + const renderLoading = () => { + return ( + + + Loading... + + + ); + }; + + const renderTableData = () => { + if ( filters.length === 0 ) { + return ( + + + No Filters Found + + + ); + } + + return filters.map( ( filter, index ) => { + // eslint-disable-next-line camelcase + const { + endpoint, + dot_notation: dotNotation, + replacement, + enabled, + } = filter; + + return ( + + { index + 1 } + { endpoint } + { dotNotation } + { replacement + '' } + { enabled + '' } + + + + + + + + ); + } ); + }; + + return ( + <> + { isNewModalOpen && ( + { + setNewModalOpen( false ); + } } + > +
+
+ + + + + + +
+ +
+
+ ) } +
+ setNewModalOpen( true ) } + /> +
+
+ + + + + + + + + + + + + + { isLoading ? renderLoading() : renderTableData() } + +
I.D + Endpoint + + Dot Notation + + Replacement + + Enabled + + Toggle +
+
+ + ); +} + +export default compose( + withSelect( ( select ) => { + const { getFilters, isLoading } = select( STORE_KEY ); + const filters = getFilters(); + + return { + filters, + isLoading: isLoading(), + }; + } ), + withDispatch( ( dispatch ) => { + const { saveFilter, deleteFilter, toggleFilter } = dispatch( + STORE_KEY + ); + + return { + saveFilter, + deleteFilter, + toggleFilter, + }; + } ) +)( RestAPIFilters ); diff --git a/plugins/woocommerce-beta-tester/admin/src/tools/README.md b/plugins/woocommerce-beta-tester/src/src/tools/README.md similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/tools/README.md rename to plugins/woocommerce-beta-tester/src/src/tools/README.md diff --git a/plugins/woocommerce-beta-tester/admin/src/tools/commands/disable-email.js b/plugins/woocommerce-beta-tester/src/src/tools/commands/disable-email.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/tools/commands/disable-email.js rename to plugins/woocommerce-beta-tester/src/src/tools/commands/disable-email.js diff --git a/plugins/woocommerce-beta-tester/admin/src/tools/commands/index.js b/plugins/woocommerce-beta-tester/src/src/tools/commands/index.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/tools/commands/index.js rename to plugins/woocommerce-beta-tester/src/src/tools/commands/index.js diff --git a/plugins/woocommerce-beta-tester/admin/src/tools/commands/trigger-cron.js b/plugins/woocommerce-beta-tester/src/src/tools/commands/trigger-cron.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/tools/commands/trigger-cron.js rename to plugins/woocommerce-beta-tester/src/src/tools/commands/trigger-cron.js diff --git a/plugins/woocommerce-beta-tester/admin/src/tools/commands/trigger-update-callbacks.js b/plugins/woocommerce-beta-tester/src/src/tools/commands/trigger-update-callbacks.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/tools/commands/trigger-update-callbacks.js rename to plugins/woocommerce-beta-tester/src/src/tools/commands/trigger-update-callbacks.js diff --git a/plugins/woocommerce-beta-tester/admin/src/tools/data/action-types.js b/plugins/woocommerce-beta-tester/src/src/tools/data/action-types.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/tools/data/action-types.js rename to plugins/woocommerce-beta-tester/src/src/tools/data/action-types.js diff --git a/plugins/woocommerce-beta-tester/admin/src/tools/data/actions.js b/plugins/woocommerce-beta-tester/src/src/tools/data/actions.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/tools/data/actions.js rename to plugins/woocommerce-beta-tester/src/src/tools/data/actions.js diff --git a/plugins/woocommerce-beta-tester/admin/src/tools/data/constants.js b/plugins/woocommerce-beta-tester/src/src/tools/data/constants.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/tools/data/constants.js rename to plugins/woocommerce-beta-tester/src/src/tools/data/constants.js diff --git a/plugins/woocommerce-beta-tester/src/src/tools/data/index.js b/plugins/woocommerce-beta-tester/src/src/tools/data/index.js new file mode 100644 index 00000000000..e476479ea88 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/src/tools/data/index.js @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import { registerStore } from '@wordpress/data'; +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'; + +export default registerStore( STORE_KEY, { + actions, + selectors, + resolvers, + controls, + reducer, +} ); diff --git a/plugins/woocommerce-beta-tester/admin/src/tools/data/reducer.js b/plugins/woocommerce-beta-tester/src/src/tools/data/reducer.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/tools/data/reducer.js rename to plugins/woocommerce-beta-tester/src/src/tools/data/reducer.js diff --git a/plugins/woocommerce-beta-tester/admin/src/tools/data/resolvers.js b/plugins/woocommerce-beta-tester/src/src/tools/data/resolvers.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/tools/data/resolvers.js rename to plugins/woocommerce-beta-tester/src/src/tools/data/resolvers.js diff --git a/plugins/woocommerce-beta-tester/admin/src/tools/data/selectors.js b/plugins/woocommerce-beta-tester/src/src/tools/data/selectors.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/tools/data/selectors.js rename to plugins/woocommerce-beta-tester/src/src/tools/data/selectors.js diff --git a/plugins/woocommerce-beta-tester/admin/src/tools/index.js b/plugins/woocommerce-beta-tester/src/src/tools/index.js similarity index 100% rename from plugins/woocommerce-beta-tester/admin/src/tools/index.js rename to plugins/woocommerce-beta-tester/src/src/tools/index.js diff --git a/plugins/woocommerce-beta-tester/src/tools/README.md b/plugins/woocommerce-beta-tester/src/tools/README.md new file mode 100644 index 00000000000..e9d4fd019b3 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/tools/README.md @@ -0,0 +1,17 @@ +# Tools + +## Adding a New Command + +1. Open `commands.js` and add a new object with command, description, and action keys. Action value must be a valid function name. +2. Open `data/actions.js` and add a function. The function name must be the value of `Action` from the first step. + +Sample function: +``` +export function* helloWorld() { + yield runCommand( 'Hello World', function* () { + console.log('Hello World'); + } ); +} +``` + +3. Run `npm start` to compile and test. diff --git a/plugins/woocommerce-beta-tester/src/tools/commands/disable-email.js b/plugins/woocommerce-beta-tester/src/tools/commands/disable-email.js new file mode 100644 index 00000000000..0aba29d4bb0 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/tools/commands/disable-email.js @@ -0,0 +1,37 @@ +/** + * External dependencies. + */ +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { STORE_KEY } from '../data/constants'; + +export const DisableEmail = () => { + const { isEmailDisabled } = useSelect( ( select ) => { + const { getIsEmailDisabled } = select( STORE_KEY ); + return { + isEmailDisabled: getIsEmailDisabled(), + }; + } ); + + const getEmailStatus = () => { + switch( isEmailDisabled ) { + case 'yes': + return 'WooCommerce emails are turned off 🔴'; + case 'no': + return 'WooCommerce emails are turned on 🟢'; + case 'error': + return 'Error 🙁'; + default: + return 'Loading ...'; + } + } + + return ( +
+ { getEmailStatus() } +
+ ); +}; diff --git a/plugins/woocommerce-beta-tester/src/tools/commands/index.js b/plugins/woocommerce-beta-tester/src/tools/commands/index.js new file mode 100644 index 00000000000..d2cef0661cf --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/tools/commands/index.js @@ -0,0 +1,67 @@ +/** + * Internal dependencies + */ +import { TriggerCronJob, TRIGGER_CRON_ACTION_NAME } from './trigger-cron'; +import { DisableEmail } from './disable-email'; +import { + TriggerUpdateCallbacks, + TRIGGER_UPDATE_CALLBACKS_ACTION_NAME, +} from './trigger-update-callbacks'; + +export default [ + { + command: 'Trigger WCA Install', + description: `This will trigger a WooCommerce Admin install, which usually + happens when a new version (or new install) of WooCommerce + Admin is installed. Triggering the install manually can + run tasks such as removing obsolete admin notes.`, + action: 'triggerWcaInstall', + }, + { + command: 'Reset Onboarding Wizard', + description: 'Resets Onboarding Wizard progress.', + action: 'resetOnboardingWizard', + }, + { + command: 'Reset Jetpack Connection', + description: 'Resets Jepack Connection options.', + action: 'resetJetpackConnection', + }, + { + command: 'Enable wc-admin Tracking', + description: + 'Enable Tracking Debug mode. You should change your console level to verbose.', + action: 'enableTrackingDebug', + }, + { + command: 'Update WC installation timestamp', + description: + 'Updates woocommerce_admin_install_timestamp to a certain date', + action: 'updateStoreAge', + }, + { + command: 'Run wc_admin_daily job', + description: 'Run wc_admin_daily job', + action: 'runWcAdminDailyJob', + }, + { + command: 'Delete all products', + description: 'Delete all products', + action: 'deleteAllProducts', + }, + { + command: 'Run a cron job', + description: , + action: TRIGGER_CRON_ACTION_NAME, + }, + { + command: 'Disable WC emails', + description: , + action: 'runDisableEmail', + }, + { + command: 'Run version update callbacks', + description: , + action: TRIGGER_UPDATE_CALLBACKS_ACTION_NAME, + }, +]; diff --git a/plugins/woocommerce-beta-tester/src/tools/commands/trigger-cron.js b/plugins/woocommerce-beta-tester/src/tools/commands/trigger-cron.js new file mode 100644 index 00000000000..fc232455ccf --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/tools/commands/trigger-cron.js @@ -0,0 +1,48 @@ +/** + * External dependencies. + */ +import { SelectControl } from '@wordpress/components'; +import { useDispatch, useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { STORE_KEY } from '../data/constants'; + +export const TRIGGER_CRON_ACTION_NAME = 'runSelectedCronJob'; + +export const TriggerCronJob = () => { + const { cronList } = useSelect((select) => { + const { getCronJobs } = select(STORE_KEY); + return { + cronList: getCronJobs(), + }; + }); + const { updateCommandParams } = useDispatch(STORE_KEY); + + function onCronChange(selectedValue) { + const { hook, signature } = cronList[selectedValue]; + updateCommandParams(TRIGGER_CRON_ACTION_NAME, { hook, signature }); + } + + function getOptions() { + return Object.keys(cronList).map((name) => { + return { label: name, value: name }; + }); + } + + return ( +
+ {!cronList ? ( +

Loading ...

+ ) : ( + + )} +
+ ); +}; diff --git a/plugins/woocommerce-beta-tester/src/tools/commands/trigger-update-callbacks.js b/plugins/woocommerce-beta-tester/src/tools/commands/trigger-update-callbacks.js new file mode 100644 index 00000000000..e830416ddc1 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/tools/commands/trigger-update-callbacks.js @@ -0,0 +1,51 @@ +/** + * External dependencies + */ +import { SelectControl } from '@wordpress/components'; +import { useDispatch, useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { STORE_KEY } from '../data/constants'; + +export const TRIGGER_UPDATE_CALLBACKS_ACTION_NAME = + 'runSelectedUpdateCallbacks'; + +export const TriggerUpdateCallbacks = () => { + const { dbUpdateVersions } = useSelect((select) => { + const { getDBUpdateVersions } = select(STORE_KEY); + return { + dbUpdateVersions: getDBUpdateVersions(), + }; + }); + + const { updateCommandParams } = useDispatch(STORE_KEY); + + function onCronChange(version) { + updateCommandParams(TRIGGER_UPDATE_CALLBACKS_ACTION_NAME, { + version, + }); + } + + function getOptions() { + return dbUpdateVersions.map((version) => { + return { label: version, value: version }; + }); + } + + return ( +
+ {!dbUpdateVersions ? ( +

Loading ...

+ ) : ( + + )} +
+ ); +}; diff --git a/plugins/woocommerce-beta-tester/src/tools/data/action-types.js b/plugins/woocommerce-beta-tester/src/tools/data/action-types.js new file mode 100644 index 00000000000..3cb725cd38a --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/tools/data/action-types.js @@ -0,0 +1,13 @@ +const TYPES = { + ADD_CURRENTLY_RUNNING: 'ADD_CURRENTLY_RUNNING', + REMOVE_CURRENTLY_RUNNING: 'REMOVE_CURRENTLY_RUNNING', + ADD_MESSAGE: 'ADD_MESSAGE', + UPDATE_MESSAGE: 'UPDATE_MESSAGE', + REMOVE_MESSAGE: 'REMOVE_MESSAGE', + ADD_COMMAND_PARAMS: 'ADD_COMMAND_PARAMS', + SET_CRON_JOBS: 'SET_CRON_JOBS', + IS_EMAIL_DISABLED: 'IS_EMAIL_DISABLED', + SET_DB_UPDATE_VERSIONS: 'SET_DB_UPDATE_VERSIONS', +}; + +export default TYPES; diff --git a/plugins/woocommerce-beta-tester/src/tools/data/actions.js b/plugins/woocommerce-beta-tester/src/tools/data/actions.js new file mode 100644 index 00000000000..c149889ab57 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/tools/data/actions.js @@ -0,0 +1,211 @@ +/** + * External dependencies + */ +import { apiFetch } from '@wordpress/data-controls'; + +/** + * Internal dependencies + */ +import TYPES from './action-types'; +import { API_NAMESPACE } from './constants'; + +export function addCurrentlyRunning(command) { + return { + type: TYPES.ADD_CURRENTLY_RUNNING, + command, + }; +} + +export function removeCurrentlyRunning(command) { + return { + type: TYPES.REMOVE_CURRENTLY_RUNNING, + command, + }; +} + +export function addMessage(source, message) { + return { + type: TYPES.ADD_MESSAGE, + source, + message, + }; +} + +export function updateMessage(source, message, status) { + return { + type: TYPES.ADD_MESSAGE, + source, + message, + status, + }; +} + +export function removeMessage(source) { + return { + type: TYPES.REMOVE_MESSAGE, + source, + }; +} + +export function updateCommandParams(source, params) { + return { + type: TYPES.ADD_COMMAND_PARAMS, + source, + params, + }; +} + +export function setCronJobs(cronJobs) { + return { + type: TYPES.SET_CRON_JOBS, + cronJobs, + }; +} + +export function setDBUpdateVersions(versions) { + return { + type: TYPES.SET_DB_UPDATE_VERSIONS, + versions, + }; +} + +export function setIsEmailDisabled(isEmailDisabled) { + return { + type: TYPES.IS_EMAIL_DISABLED, + isEmailDisabled, + }; +} + +function* runCommand(commandName, func) { + try { + 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); + } +} + +export function* triggerWcaInstall() { + 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* () { + const optionsToDelete = [ + 'woocommerce_task_list_tracked_completed_tasks', + 'woocommerce_onboarding_profile', + '_transient_wc_onboarding_themes', + ]; + yield apiFetch({ + method: 'DELETE', + path: `${API_NAMESPACE}/options/${optionsToDelete.join(',')}`, + }); + }); +} + +export function* resetJetpackConnection() { + yield runCommand('Reset Jetpack Connection', function* () { + yield apiFetch({ + method: 'DELETE', + path: `${API_NAMESPACE}/options/jetpack_options`, + }); + }); +} + +export function* enableTrackingDebug() { + yield runCommand('Enable WC Admin Tracking Debug Mode', function* () { + window.localStorage.setItem('debug', 'wc-admin:*'); + }); +} + +export function* updateStoreAge() { + 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 yyyy = today.getFullYear(); + + // eslint-disable-next-line no-alert + const numberOfDays = window.prompt( + 'Please enter a date in yyyy/mm/dd format', + yyyy + '/' + mm + '/' + dd + ); + + if (numberOfDays !== null) { + const dates = numberOfDays.split('/'); + const newTimestamp = Math.round( + new Date(dates[0], dates[1] - 1, dates[2]).getTime() / 1000 + ); + const payload = { + woocommerce_admin_install_timestamp: JSON.parse(newTimestamp), + }; + yield apiFetch({ + method: 'POST', + path: '/wc-admin/options', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(payload), + }); + } + }); +} + +export function* runWcAdminDailyJob() { + 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?')) { + return; + } + + 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, + }); + }); +} + +export function* runSelectedUpdateCallbacks(params) { + yield runCommand('Run version update callbacks', function* () { + yield apiFetch({ + path: API_NAMESPACE + '/tools/trigger-selected-update-callbacks/v1', + method: 'POST', + data: params, + }); + }); +} + +export function* runDisableEmail() { + yield runCommand('Disable/Enable WooCommerce emails', function* () { + const response = yield apiFetch({ + path: `${API_NAMESPACE}/tools/toggle-emails/v1`, + method: 'POST', + }); + yield setIsEmailDisabled( response ); + }); +} diff --git a/plugins/woocommerce-beta-tester/src/tools/data/constants.js b/plugins/woocommerce-beta-tester/src/tools/data/constants.js new file mode 100644 index 00000000000..c7599691985 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/tools/data/constants.js @@ -0,0 +1,2 @@ +export const STORE_KEY = 'wc-admin-helper/tools'; +export const API_NAMESPACE = '/wc-admin-test-helper'; diff --git a/plugins/woocommerce-beta-tester/src/tools/data/index.js b/plugins/woocommerce-beta-tester/src/tools/data/index.js new file mode 100644 index 00000000000..e476479ea88 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/tools/data/index.js @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import { registerStore } from '@wordpress/data'; +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'; + +export default registerStore( STORE_KEY, { + actions, + selectors, + resolvers, + controls, + reducer, +} ); diff --git a/plugins/woocommerce-beta-tester/src/tools/data/reducer.js b/plugins/woocommerce-beta-tester/src/tools/data/reducer.js new file mode 100644 index 00000000000..7756dfcec6c --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/tools/data/reducer.js @@ -0,0 +1,88 @@ +/** + * Internal dependencies + */ +import TYPES from './action-types'; + +const DEFAULT_STATE = { + currentlyRunning: {}, + errorMessages: [], + cronJobs: false, + isEmailDisabled: '', + messages: {}, + params: [], + status: '', + dbUpdateVersions: [], +}; + +const reducer = ( state = DEFAULT_STATE, action ) => { + switch ( action.type ) { + case TYPES.ADD_MESSAGE: + if ( ! action.status ) { + action.status = 'info'; + } + return { + ...state, + messages: { + ...state.messages, + [ action.source ]: { + message: action.message, + status: action.status, + }, + }, + }; + case TYPES.REMOVE_MESSAGE: + const messages = { ...state.messages }; + delete messages[ action.source ]; + return { + ...state, + messages, + }; + case TYPES.SET_STATUS: + return { + ...state, + status: action.status, + }; + case TYPES.ADD_CURRENTLY_RUNNING: + return { + ...state, + currentlyRunning: { + ...state, + [ action.command ]: true, + }, + }; + case TYPES.REMOVE_CURRENTLY_RUNNING: + return { + ...state, + currentlyRunning: { + ...state, + [ action.command ]: false, + }, + }; + case TYPES.SET_CRON_JOBS: + return { + ...state, + cronJobs: action.cronJobs, + }; + case TYPES.IS_EMAIL_DISABLED: + return { + ...state, + isEmailDisabled: action.isEmailDisabled, + }; + case TYPES.ADD_COMMAND_PARAMS: + return { + ...state, + params: { + [ action.source ]: action.params, + }, + }; + case TYPES.SET_DB_UPDATE_VERSIONS: + return { + ...state, + dbUpdateVersions: action.versions, + }; + default: + return state; + } +}; + +export default reducer; diff --git a/plugins/woocommerce-beta-tester/src/tools/data/resolvers.js b/plugins/woocommerce-beta-tester/src/tools/data/resolvers.js new file mode 100644 index 00000000000..bca43fb594a --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/tools/data/resolvers.js @@ -0,0 +1,57 @@ +/** + * External dependencies + */ +import { apiFetch } from '@wordpress/data-controls'; + +/** + * Internal dependencies + */ +import { API_NAMESPACE } from './constants'; +import { + setCronJobs, + setDBUpdateVersions, + setIsEmailDisabled, +} from './actions'; + +export function* getCronJobs() { + const path = `${ API_NAMESPACE }/tools/get-cron-list/v1`; + + try { + const response = yield apiFetch( { + path, + method: 'GET', + } ); + yield setCronJobs( response ); + } catch ( error ) { + throw new Error( error ); + } +} + +export function* getDBUpdateVersions() { + const path = `${API_NAMESPACE}/tools/get-update-versions/v1`; + + try { + const response = yield apiFetch({ + path, + method: 'GET', + }); + yield setDBUpdateVersions(response); + } catch (error) { + throw new Error(error); + } +} + +export function* getIsEmailDisabled() { + const path = `${API_NAMESPACE}/tools/get-email-status/v1`; + + try { + const response = yield apiFetch( { + path, + method: 'GET', + } ); + yield setIsEmailDisabled( response ); + } catch ( error ) { + yield setIsEmailDisabled( 'error' ); + throw new Error( error ); + } +} diff --git a/plugins/woocommerce-beta-tester/src/tools/data/selectors.js b/plugins/woocommerce-beta-tester/src/tools/data/selectors.js new file mode 100644 index 00000000000..9fdeabe0760 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/tools/data/selectors.js @@ -0,0 +1,27 @@ +export function getCurrentlyRunning( state ) { + return state.currentlyRunning; +} + +export function getMessages( state ) { + return state.messages; +} + +export function getStatus( state ) { + return state.status; +} + +export function getCommandParams( state ) { + return state.params; +} + +export function getCronJobs( state ) { + return state.cronJobs; +} + +export function getIsEmailDisabled( state ) { + return state.isEmailDisabled; +} + +export function getDBUpdateVersions(state) { + return state.dbUpdateVersions; +} diff --git a/plugins/woocommerce-beta-tester/src/tools/index.js b/plugins/woocommerce-beta-tester/src/tools/index.js new file mode 100644 index 00000000000..5ba21f88b0c --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/tools/index.js @@ -0,0 +1,87 @@ +/** + * External dependencies + */ +import { withDispatch, withSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; +import { Notice, Button } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { default as commands } from './commands'; +import { STORE_KEY } from './data/constants'; +import './data'; + +function Tools( { actions, currentlyRunningCommands, messages, comandParams } ) { + actions = actions(); + return ( +
+

Tools

+

This section contains miscellaneous tools.

+ { Object.keys( messages ).map( ( key ) => { + return ( + + { key }: { messages[ key ].message } + + ); + } ) } + + + + + + + + + + { commands.map( ( { action, command, description }, index ) => { + const params = comandParams[ action ] ?? false; + return ( + + + + + + ); + } ) } + +
CommandDescriptionRun
{ command }{ description } + +
+
+ ); +} + +export default compose( + withSelect( ( select ) => { + const { getCurrentlyRunning, getMessages, getCommandParams } = select( STORE_KEY ); + return { + currentlyRunningCommands: getCurrentlyRunning(), + messages: getMessages(), + comandParams: getCommandParams(), + }; + } ), + withDispatch( ( dispatch ) => { + const actions = function () { + return dispatch( STORE_KEY ); + }; + + return { + actions, + }; + } ) +)( Tools ); diff --git a/plugins/woocommerce-beta-tester/webpack.config.js b/plugins/woocommerce-beta-tester/webpack.config.js new file mode 100644 index 00000000000..7c211bc7ca0 --- /dev/null +++ b/plugins/woocommerce-beta-tester/webpack.config.js @@ -0,0 +1,13 @@ +const defaultConfig = require( '@wordpress/scripts/config/webpack.config' ); +const WooCommerceDependencyExtractionWebpackPlugin = require( '@woocommerce/dependency-extraction-webpack-plugin' ); + +module.exports = { + ...defaultConfig, + plugins: [ + ...defaultConfig.plugins.filter( + ( plugin ) => + plugin.constructor.name !== 'DependencyExtractionWebpackPlugin' + ), + new WooCommerceDependencyExtractionWebpackPlugin(), + ], +};