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 00000000000..1959ae3e6d1 Binary files /dev/null and b/plugins/woocommerce-beta-tester/images/images/admin-notes/banner.jpg differ 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 00000000000..f08bbc9f62d Binary files /dev/null and b/plugins/woocommerce-beta-tester/images/images/admin-notes/thumbnail.jpg differ 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 00000000000..d67c209ae99 Binary files /dev/null and b/plugins/woocommerce-beta-tester/images/images/admin-notes/woocommerce-logo-vector.png differ 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(), + ], +};