Merge pull request #33329 from woocommerce/add/admin-tester
Add WooCommerce Admin Test Helper
This commit is contained in:
commit
dc2977cea3
|
@ -13,5 +13,4 @@ phpcs.xml
|
|||
# build files
|
||||
woocommerce-beta-tester.zip
|
||||
node_modules/
|
||||
build/
|
||||
bin/
|
||||
|
|
|
@ -1 +1,5 @@
|
|||
*.min.js
|
||||
build
|
||||
build-module
|
||||
node_modules
|
||||
vendor
|
||||
|
|
|
@ -1,25 +1,13 @@
|
|||
{
|
||||
"root": true,
|
||||
"extends": [ "plugin:@woocommerce/eslint-plugin/recommended" ],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
"globals": {
|
||||
"wp": true,
|
||||
"es6": true
|
||||
},
|
||||
"rules": {
|
||||
"camelcase": 0,
|
||||
"indent": 0,
|
||||
"max-len": [
|
||||
2,
|
||||
{
|
||||
"code": 140
|
||||
}
|
||||
],
|
||||
"no-console": 1
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"no-alert": "off"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,3 +20,6 @@ node_modules/
|
|||
# Built assets
|
||||
build/
|
||||
woocommerce-beta-tester.zip
|
||||
build
|
||||
build-module
|
||||
build-style
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
# WooCommerce Admin Test Helper
|
||||
|
||||
A plugin that makes it easier to test the WooCommerce Admin plugin.
|
||||
|
||||
## Development
|
||||
|
||||
To get started, run the following commands:
|
||||
|
||||
```text
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
See [wp-scripts](https://github.com/WordPress/gutenberg/tree/master/packages/scripts) for more usage information.
|
||||
|
||||
## Extending
|
||||
|
||||
There are two client-side filters available if you want to extend the test
|
||||
helper with your own plugin's test setup code.
|
||||
|
||||
This example adds a new tab:
|
||||
|
||||
```
|
||||
import { addFilter } from '@wordpress/hooks';
|
||||
|
||||
const SuperSekret = () => (
|
||||
<>
|
||||
<h2>Super sekret</h2>
|
||||
<p>This section contains super sekret tools.</p>
|
||||
<NewTool/>
|
||||
</>
|
||||
);
|
||||
addFilter(
|
||||
'woocommerce_admin_test_helper_tabs',
|
||||
'wath',
|
||||
( tabs ) => [
|
||||
...tabs,
|
||||
{
|
||||
name: 'super-sekret',
|
||||
title: 'Super sekret',
|
||||
content: <SuperSekret/>,
|
||||
}
|
||||
]
|
||||
);
|
||||
```
|
||||
|
||||
This example adds a new tool to the existing Options tab:
|
||||
|
||||
```
|
||||
import { addFilter } from '@wordpress/hooks';
|
||||
|
||||
const NewTool = () => (
|
||||
<>
|
||||
<strong>New tool</strong>
|
||||
<p>Description</p>
|
||||
<button>Execute</button>
|
||||
</>
|
||||
);
|
||||
addFilter(
|
||||
'woocommerce_admin_test_helper_tab_options',
|
||||
'wath',
|
||||
( entries ) => [
|
||||
...entries,
|
||||
<NewTool/>
|
||||
]
|
||||
);
|
||||
```
|
||||
|
||||
Register a REST API endpoint to perform server-side actions in the usual way:
|
||||
|
||||
```
|
||||
add_action( 'rest_api_init', function() {
|
||||
register_rest_route(
|
||||
'your-plugin/v1',
|
||||
'/area/action',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => 'your_plugin_area_action',
|
||||
'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', 'your-plugin' )
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
)
|
||||
);
|
||||
} );
|
||||
|
||||
function your_plugin_area_action() {
|
||||
return [];
|
||||
}
|
||||
```
|
||||
|
||||
This would be used on the client like this:
|
||||
|
||||
```
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
...
|
||||
const response = await apiFetch( {
|
||||
path: '/your-plugin/v1/area/action',
|
||||
method: 'POST',
|
||||
} );
|
||||
```
|
||||
|
||||
### Deploying
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- [Hub](https://github.com/github/hub)
|
||||
- Write access to this repository
|
||||
|
||||
You can create a test ZIP of the plugin using this command:
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
This creates `woocommerce-admin-test-helper.zip` in the project root.
|
||||
|
||||
We release the plugin using GitHub Releases. There is a script to automate this:
|
||||
|
||||
0. Make sure the version is updated in `woocommerce-admin-test-helper.php` and `package.json`
|
||||
1. Commit and push to `trunk`
|
||||
2. Run `npm run release`
|
||||
3. Make sure you provide the correct version number when prompted
|
||||
4. That's it!
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
use Automattic\WooCommerce\Admin\Notes\Note;
|
||||
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/admin-notes/add-note/v1',
|
||||
'admin_notes_add_note'
|
||||
);
|
||||
|
||||
|
||||
function admin_notes_add_note( $request ) {
|
||||
$note = new Note();
|
||||
$mock_note_data = get_mock_note_data();
|
||||
$type = $request->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' => ''
|
||||
)
|
||||
);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/admin-notes/delete-all-notes/v1',
|
||||
'admin_notes_delete_all_notes'
|
||||
);
|
||||
|
||||
function admin_notes_delete_all_notes() {
|
||||
global $wpdb;
|
||||
|
||||
$deleted_note_count = $wpdb->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,
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
function register_woocommerce_admin_test_helper_rest_route( $route, $callback, $additional_options = array() ) {
|
||||
add_action( 'rest_api_init', function() use ( $route, $callback, $additional_options ) {
|
||||
|
||||
$default_options = array(
|
||||
'methods' => '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' );
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
use Automattic\WooCommerce\Admin\Notes\Note;
|
||||
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/admin-notes/add-note/v1',
|
||||
'admin_notes_add_note'
|
||||
);
|
||||
|
||||
|
||||
function admin_notes_add_note( $request ) {
|
||||
$note = new Note();
|
||||
$mock_note_data = get_mock_note_data();
|
||||
$type = $request->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' => ''
|
||||
)
|
||||
);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/admin-notes/delete-all-notes/v1',
|
||||
'admin_notes_delete_all_notes'
|
||||
);
|
||||
|
||||
function admin_notes_delete_all_notes() {
|
||||
global $wpdb;
|
||||
|
||||
$deleted_note_count = $wpdb->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,
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
function register_woocommerce_admin_test_helper_rest_route( $route, $callback, $additional_options = array() ) {
|
||||
add_action( 'rest_api_init', function() use ( $route, $callback, $additional_options ) {
|
||||
|
||||
$default_options = array(
|
||||
'methods' => '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' );
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
|
||||
const OPTION_NAME_PREFIX = 'wc_admin_helper_feature_values';
|
||||
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/features/(?P<feature_name>[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();
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/options',
|
||||
'wca_test_helper_get_options',
|
||||
array(
|
||||
'methods' => '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<option_names>(.*)+)',
|
||||
'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 );
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
$filters = get_option(WCA_Test_Helper_Rest_Api_Filters::WC_ADMIN_TEST_HELPER_REST_API_FILTER_OPTION, [] );
|
||||
|
||||
function array_dot_set( &$array, $key, $value ) {
|
||||
if ( is_null( $key ) ) {
|
||||
return $array = $value;
|
||||
}
|
||||
|
||||
$keys = explode('.', $key);
|
||||
|
||||
while ( count($keys) > 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
|
||||
);
|
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/rest-api-filters',
|
||||
[ WCA_Test_Helper_Rest_Api_Filters::class, 'create' ],
|
||||
array(
|
||||
'methods' => '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<index>\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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/tools/delete-all-products/v1',
|
||||
'tools_delete_all_products'
|
||||
);
|
||||
|
||||
function tools_delete_all_products() {
|
||||
$query = new \WC_Product_Query();
|
||||
$products = $query->get_products();
|
||||
|
||||
foreach ( $products as $product ) {
|
||||
$product->delete( true );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/tools/toggle-emails/v1',
|
||||
'toggle_emails'
|
||||
);
|
||||
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/tools/get-email-status/v1',
|
||||
'get_email_status',
|
||||
array(
|
||||
'methods' => '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' ) );
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/tools/run-wc-admin-daily/v1',
|
||||
'tools_run_wc_admin_daily'
|
||||
);
|
||||
|
||||
function tools_run_wc_admin_daily() {
|
||||
do_action('wc_admin_daily');
|
||||
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/tools/get-cron-list/v1',
|
||||
'tools_get_cron_list',
|
||||
array(
|
||||
'methods' => '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 );
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Cache;
|
||||
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/tools/get-update-versions/v1',
|
||||
'tools_get_wc_admin_versions',
|
||||
array(
|
||||
'methods' => '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;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/tools/trigger-wca-install/v1',
|
||||
'tools_trigger_wca_install'
|
||||
);
|
||||
|
||||
function tools_trigger_wca_install() {
|
||||
\WC_Install::install();
|
||||
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* A class for logging tracked events.
|
||||
*/
|
||||
class TracksDebugLog {
|
||||
/**
|
||||
* Logger class to use.
|
||||
*
|
||||
* @var WC_Logger_Interface|null
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* Logger source.
|
||||
*
|
||||
* @var string logger source.
|
||||
*/
|
||||
private $source = 'tracks';
|
||||
|
||||
/**
|
||||
* Initialize hooks.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_filter( 'woocommerce_tracks_event_properties', array( $this, 'log_event' ), 10, 2 );
|
||||
$logger = wc_get_logger();
|
||||
$this->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();
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
|
||||
const OPTION_NAME_PREFIX = 'wc_admin_helper_feature_values';
|
||||
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/features/(?P<feature_name>[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();
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/options',
|
||||
'wca_test_helper_get_options',
|
||||
array(
|
||||
'methods' => '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<option_names>(.*)+)',
|
||||
'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 );
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
$filters = get_option(WCA_Test_Helper_Rest_Api_Filters::WC_ADMIN_TEST_HELPER_REST_API_FILTER_OPTION, [] );
|
||||
|
||||
function array_dot_set( &$array, $key, $value ) {
|
||||
if ( is_null( $key ) ) {
|
||||
return $array = $value;
|
||||
}
|
||||
|
||||
$keys = explode('.', $key);
|
||||
|
||||
while ( count($keys) > 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
|
||||
);
|
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/rest-api-filters',
|
||||
[ WCA_Test_Helper_Rest_Api_Filters::class, 'create' ],
|
||||
array(
|
||||
'methods' => '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<index>\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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/tools/delete-all-products/v1',
|
||||
'tools_delete_all_products'
|
||||
);
|
||||
|
||||
function tools_delete_all_products() {
|
||||
$query = new \WC_Product_Query();
|
||||
$products = $query->get_products();
|
||||
|
||||
foreach ( $products as $product ) {
|
||||
$product->delete( true );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/tools/toggle-emails/v1',
|
||||
'toggle_emails'
|
||||
);
|
||||
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/tools/get-email-status/v1',
|
||||
'get_email_status',
|
||||
array(
|
||||
'methods' => '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' ) );
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/tools/run-wc-admin-daily/v1',
|
||||
'tools_run_wc_admin_daily'
|
||||
);
|
||||
|
||||
function tools_run_wc_admin_daily() {
|
||||
do_action('wc_admin_daily');
|
||||
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/tools/get-cron-list/v1',
|
||||
'tools_get_cron_list',
|
||||
array(
|
||||
'methods' => '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 );
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Cache;
|
||||
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/tools/get-update-versions/v1',
|
||||
'tools_get_wc_admin_versions',
|
||||
array(
|
||||
'methods' => '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;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/tools/trigger-wca-install/v1',
|
||||
'tools_trigger_wca_install'
|
||||
);
|
||||
|
||||
function tools_trigger_wca_install() {
|
||||
\WC_Install::install();
|
||||
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* A class for logging tracked events.
|
||||
*/
|
||||
class TracksDebugLog {
|
||||
/**
|
||||
* Logger class to use.
|
||||
*
|
||||
* @var WC_Logger_Interface|null
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* Logger source.
|
||||
*
|
||||
* @var string logger source.
|
||||
*/
|
||||
private $source = 'tracks';
|
||||
|
||||
/**
|
||||
* Initialize hooks.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_filter( 'woocommerce_tracks_event_properties', array( $this, 'log_event' ), 10, 2 );
|
||||
$logger = wc_get_logger();
|
||||
$this->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();
|
|
@ -1,44 +1,51 @@
|
|||
/**
|
||||
* Handles version information modal.
|
||||
*
|
||||
* @package WooCommerceBetaTester\JS
|
||||
* @package
|
||||
*/
|
||||
|
||||
jQuery(function( $ ) {
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
jQuery( function ( $ ) {
|
||||
/**
|
||||
* Version information
|
||||
*/
|
||||
var wc_beta_tester_version_information = {
|
||||
|
||||
const wc_beta_tester_version_information = {
|
||||
/**
|
||||
* Initialize Version Information click
|
||||
*/
|
||||
init: function() {
|
||||
$( '#wp-admin-bar-show-version-info' )
|
||||
.on( 'click', this.showModal );
|
||||
init() {
|
||||
$( '#wp-admin-bar-show-version-info' ).on(
|
||||
'click',
|
||||
this.showModal
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for showing/hiding version information modal
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
showModal: function( event ) {
|
||||
showModal( event ) {
|
||||
event.preventDefault();
|
||||
|
||||
// Prevent multiple modals.
|
||||
if ( 0 < $( '.wc-backbone-modal-beta-tester-version-info' ).length ) {
|
||||
if (
|
||||
$( '.wc-backbone-modal-beta-tester-version-info' ).length > 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$( this ).WCBackboneModal({
|
||||
$( this ).WCBackboneModal( {
|
||||
template: 'wc-beta-tester-version-info',
|
||||
variable: {
|
||||
// eslint-disable-next-line no-undef
|
||||
version: wc_beta_tester_version_info_params.version,
|
||||
// eslint-disable-next-line no-undef
|
||||
description: wc_beta_tester_version_info_params.description,
|
||||
},
|
||||
});
|
||||
}
|
||||
} );
|
||||
},
|
||||
};
|
||||
|
||||
wc_beta_tester_version_information.init();
|
||||
});
|
||||
} );
|
||||
|
|
|
@ -1 +1 @@
|
|||
jQuery(function(i){({init:function(){i("#wp-admin-bar-show-version-info").on("click",this.showModal)},showModal:function(e){e.preventDefault(),0<i(".wc-backbone-modal-beta-tester-version-info").length||i(this).WCBackboneModal({template:"wc-beta-tester-version-info",variable:{version:wc_beta_tester_version_info_params.version,description:wc_beta_tester_version_info_params.description}})}}).init()});
|
||||
jQuery(function(i){const e={init(){i("#wp-admin-bar-show-version-info").on("click",this.showModal)},showModal(e){e.preventDefault(),0<i(".wc-backbone-modal-beta-tester-version-info").length||i(this).WCBackboneModal({template:"wc-beta-tester-version-info",variable:{version:wc_beta_tester_version_info_params.version,description:wc_beta_tester_version_info_params.description}})}};e.init()});
|
||||
|
|
|
@ -1,57 +1,69 @@
|
|||
/**
|
||||
* Handles the version picker form.
|
||||
*
|
||||
* @package WooCommerceBetaTester\JS
|
||||
* @package
|
||||
*/
|
||||
|
||||
jQuery(function( $ ) {
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
jQuery( function ( $ ) {
|
||||
/**
|
||||
* Version picker
|
||||
*/
|
||||
var wc_beta_tester_version_picker = {
|
||||
|
||||
const wc_beta_tester_version_picker = {
|
||||
/**
|
||||
* Initialize Version Information click
|
||||
*/
|
||||
init: function() {
|
||||
instance = this;
|
||||
init() {
|
||||
const instance = this;
|
||||
instance.new_version = undefined;
|
||||
|
||||
$( '#wcbt-modal-version-switch-confirm' )
|
||||
.on( 'click', this.showConfirmVersionSwitchModal );
|
||||
$( 'input[type=radio][name=wcbt_switch_to_version]' ).change( function() {
|
||||
if ( $( this ).is( ':checked' ) ) {
|
||||
instance.new_version = $( this ).val();
|
||||
}
|
||||
} ).trigger( 'change' );
|
||||
$( '#wcbt-modal-version-switch-confirm' ).on(
|
||||
'click',
|
||||
this.showConfirmVersionSwitchModal
|
||||
);
|
||||
$( 'input[type=radio][name=wcbt_switch_to_version]' )
|
||||
.change( function () {
|
||||
if ( $( this ).is( ':checked' ) ) {
|
||||
instance.new_version = $( this ).val();
|
||||
}
|
||||
} )
|
||||
.trigger( 'change' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for showing/hiding version switch modal
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
showConfirmVersionSwitchModal: function( event ) {
|
||||
showConfirmVersionSwitchModal( event ) {
|
||||
event.preventDefault();
|
||||
|
||||
const instance = this;
|
||||
|
||||
if ( ! instance.new_version ) {
|
||||
// eslint-disable-next-line no-undef
|
||||
alert( wc_beta_tester_version_picker_params.i18n_pick_version );
|
||||
} else {
|
||||
$( this ).WCBackboneModal({
|
||||
$( this ).WCBackboneModal( {
|
||||
template: 'wcbt-version-switch-confirm',
|
||||
variable: {
|
||||
new_version: instance.new_version,
|
||||
},
|
||||
});
|
||||
} );
|
||||
|
||||
$( '#wcbt-submit-version-switch' )
|
||||
.on( 'click', instance.submitSwitchVersionForm );
|
||||
$( '#wcbt-submit-version-switch' ).on(
|
||||
'click',
|
||||
instance.submitSwitchVersionForm
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Submit form to switch version of WooCommerce.
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
submitSwitchVersionForm: function( event ) {
|
||||
submitSwitchVersionForm( event ) {
|
||||
event.preventDefault();
|
||||
|
||||
$( 'form[name=wcbt-select-version]' ).get( 0 ).submit();
|
||||
|
@ -59,5 +71,4 @@ jQuery(function( $ ) {
|
|||
};
|
||||
|
||||
wc_beta_tester_version_picker.init();
|
||||
|
||||
});
|
||||
} );
|
||||
|
|
|
@ -1 +1 @@
|
|||
jQuery(function(n){({init:function(){instance=this,instance.new_version=void 0,n("#wcbt-modal-version-switch-confirm").on("click",this.showConfirmVersionSwitchModal),n("input[type=radio][name=wcbt_switch_to_version]").change(function(){n(this).is(":checked")&&(instance.new_version=n(this).val())}).trigger("change")},showConfirmVersionSwitchModal:function(i){i.preventDefault(),instance.new_version?(n(this).WCBackboneModal({template:"wcbt-version-switch-confirm",variable:{new_version:instance.new_version}}),n("#wcbt-submit-version-switch").on("click",instance.submitSwitchVersionForm)):alert(wc_beta_tester_version_picker_params.i18n_pick_version)},submitSwitchVersionForm:function(i){i.preventDefault(),n("form[name=wcbt-select-version]").get(0).submit()}}).init()});
|
||||
jQuery(function(e){const i={init(){const i=this;i.new_version=void 0,e("#wcbt-modal-version-switch-confirm").on("click",this.showConfirmVersionSwitchModal),e("input[type=radio][name=wcbt_switch_to_version]").change(function(){e(this).is(":checked")&&(i.new_version=e(this).val())}).trigger("change")},showConfirmVersionSwitchModal(i){i.preventDefault();i=this;i.new_version?(e(this).WCBackboneModal({template:"wcbt-version-switch-confirm",variable:{new_version:i.new_version}}),e("#wcbt-submit-version-switch").on("click",i.submitSwitchVersionForm)):alert(wc_beta_tester_version_picker_params.i18n_pick_version)},submitSwitchVersionForm(i){i.preventDefault(),e("form[name=wcbt-select-version]").get(0).submit()}};i.init()});
|
||||
|
|
|
@ -12,7 +12,7 @@ mkdir -p "$DEST_PATH"
|
|||
echo "Installing PHP and JS dependencies..."
|
||||
pnpm install
|
||||
echo "Running JS Build..."
|
||||
pnpm run uglify
|
||||
pnpm run build
|
||||
|
||||
echo "Syncing files..."
|
||||
rsync -rc --exclude-from="$PROJECT_PATH/.distignore" "$PROJECT_PATH/" "$DEST_PATH/" --delete --delete-excluded
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add WooCommerce Admin Helper Tester functionality to Beta Tester
|
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.8 KiB |
|
@ -13,9 +13,23 @@
|
|||
"build_step": "pnpm run build:zip"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@woocommerce/dependency-extraction-webpack-plugin": "workspace:*",
|
||||
"@woocommerce/eslint-plugin": "workspace:*",
|
||||
"@wordpress/scripts": "^19.2.4",
|
||||
"eslint": "5.16.0",
|
||||
"uglify-js": "^3.5.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/prop-types": "^15.7.4",
|
||||
"@woocommerce/data": "workspace:*",
|
||||
"@wordpress/api-fetch": "^3.21.5",
|
||||
"@wordpress/components": "^12.0.7",
|
||||
"@wordpress/compose": "^3.24.4",
|
||||
"@wordpress/data": "^4.26.7",
|
||||
"@wordpress/data-controls": "^1.20.7",
|
||||
"@wordpress/element": "^2.19.1",
|
||||
"@wordpress/hooks": "^2.11.1"
|
||||
},
|
||||
"assets": {
|
||||
"js": {
|
||||
"min": "assets/js/*.min.js",
|
||||
|
@ -25,12 +39,25 @@
|
|||
"scripts": {
|
||||
"postinstall": "composer install",
|
||||
"changelog": "composer exec -- changelogger",
|
||||
"build": "pnpm run uglify",
|
||||
"build": "pnpm run build:admin && pnpm run uglify",
|
||||
"build:admin": "wp-scripts build",
|
||||
"build:zip": "./bin/build-zip.sh",
|
||||
"build:dev": "pnpm run lint:js && pnpm run uglify",
|
||||
"build:dev": "pnpm run lint:js && pnpm run build",
|
||||
"uglify": "rm -f $npm_package_assets_js_min && for f in $npm_package_assets_js_js; do file=${f%.js}; node_modules/.bin/uglifyjs $f -c -m > $file.min.js; done",
|
||||
"lint": "eslint assets/js --ext=js",
|
||||
"lint:fix": "eslint assets/js --ext=js --fix"
|
||||
"check-engines": "wp-scripts check-engines",
|
||||
"check-licenses": "wp-scripts check-licenses",
|
||||
"format:js": "wp-scripts format-js",
|
||||
"lint:css": "wp-scripts lint-style",
|
||||
"lint:css:fix": "wp-scripts lint-style --fix",
|
||||
"lint:js": "wp-scripts lint-js",
|
||||
"lint:js:fix": "wp-scripts lint-js --fix",
|
||||
"lint:md:docs": "wp-scripts lint-md-docs",
|
||||
"lint:md:js": "wp-scripts lint-md-js",
|
||||
"lint:pkg-json": "wp-scripts lint-pkg-json",
|
||||
"packages-update": "wp-scripts packages-update",
|
||||
"start": "wp-scripts start",
|
||||
"test:e2e": "wp-scripts test-e2e",
|
||||
"test:unit": "wp-scripts test-unit-js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.15.0",
|
||||
|
@ -45,8 +72,11 @@
|
|||
"php -d display_errors=1 -l",
|
||||
"composer --working-dir=./plugins/woocommerce-beta-tester run-script phpcs-pre-commit"
|
||||
],
|
||||
"!(*min).js": [
|
||||
"pnpm lint:fix"
|
||||
"*.(t|j)s?(x)": [
|
||||
"npm run lint:js:fix"
|
||||
],
|
||||
"*.scss": [
|
||||
"npm run lint:css:fix"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
add_action( 'admin_menu', function() {
|
||||
add_management_page(
|
||||
'WooCommerce Admin Test Helper',
|
||||
'WCA Test Helper',
|
||||
'install_plugins',
|
||||
'woocommerce-admin-test-helper',
|
||||
function() {
|
||||
?><div id="woocommerce-admin-test-helper-app-root"></div><?php
|
||||
}
|
||||
);
|
||||
} );
|
||||
|
||||
add_action( 'wp_loaded', function() {
|
||||
require( 'api/api.php' );
|
||||
} );
|
||||
|
||||
add_filter( 'woocommerce_admin_get_feature_config', function( $feature_config ) {
|
||||
$custom_feature_values = get_option( 'wc_admin_helper_feature_values', array() );
|
||||
foreach ( $custom_feature_values as $feature => $value ) {
|
||||
if ( isset( $feature_config[$feature] ) ) {
|
||||
$feature_config[$feature] = $value;
|
||||
}
|
||||
}
|
||||
return $feature_config;
|
||||
} );
|
|
@ -0,0 +1,138 @@
|
|||
/**
|
||||
* 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 <strong>email</strong> note. Enable
|
||||
email insights{ ' ' }
|
||||
<a href="/wp-admin/admin.php?page=wc-settings&tab=email">
|
||||
here
|
||||
</a>{ ' ' }
|
||||
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 (
|
||||
<>
|
||||
<p>
|
||||
<strong>Add a note</strong>
|
||||
</p>
|
||||
<div>
|
||||
{ getAddNoteDescription() }
|
||||
<br />
|
||||
<div className="woocommerce-admin-test-helper__add-notes">
|
||||
<Button
|
||||
onClick={ triggerAddNote }
|
||||
disabled={ isAdding }
|
||||
isPrimary
|
||||
>
|
||||
Add admin note
|
||||
</Button>
|
||||
<SelectControl
|
||||
label="Type"
|
||||
onChange={ onTypeChange }
|
||||
labelPosition="side"
|
||||
options={ [
|
||||
{ label: 'Info', value: 'info' },
|
||||
{ label: 'Update', value: 'update' },
|
||||
{ label: 'Email', value: 'email' },
|
||||
] }
|
||||
value={ noteType }
|
||||
/>
|
||||
<SelectControl
|
||||
label="Layout"
|
||||
onChange={ onLayoutChange }
|
||||
labelPosition="side"
|
||||
options={ [
|
||||
{ label: 'Plain', value: 'plain' },
|
||||
{ label: 'Banner', value: 'banner' },
|
||||
{ label: 'Thumbnail', value: 'thumbnail' },
|
||||
] }
|
||||
disabled={ noteType !== 'info' }
|
||||
value={ noteLayout }
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<span className="woocommerce-admin-test-helper__action-status">
|
||||
{ isAdding && 'Adding, please wait' }
|
||||
{ hasAdded && 'Note added' }
|
||||
{ errorMessage && (
|
||||
<>
|
||||
<strong>Error:</strong> { errorMessage }
|
||||
</>
|
||||
) }
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { DeleteAllNotes } from './delete-all-notes';
|
||||
import { AddNote } from './add-note';
|
||||
|
||||
export const AdminNotes = () => {
|
||||
return (
|
||||
<>
|
||||
<h2>Admin notes</h2>
|
||||
<p>This section contains tools for managing admin notes.</p>
|
||||
<AddNote />
|
||||
<DeleteAllNotes />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* 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 (
|
||||
<>
|
||||
<p>
|
||||
<strong>Delete all admin notes</strong>
|
||||
</p>
|
||||
<p>
|
||||
This will delete all notes from the{ ' ' }
|
||||
<code>wp_wc_admin_notes</code>
|
||||
table, and actions from the{ ' ' }
|
||||
<code>wp_wc_admin_note_actions</code>
|
||||
table.
|
||||
<br />
|
||||
<Button
|
||||
onClick={ triggerDeleteAllNotes }
|
||||
disabled={ isDeleting }
|
||||
isPrimary
|
||||
>
|
||||
Delete all notes
|
||||
</Button>
|
||||
<br />
|
||||
<span className="woocommerce-admin-test-helper__action-status">
|
||||
{ isDeleting && 'Deleting, please wait.' }
|
||||
{ deleteStatus && (
|
||||
<>
|
||||
Deleted{ ' ' }
|
||||
<strong>{ deleteStatus.deleted_note_count }</strong>{ ' ' }
|
||||
admin notes and{ ' ' }
|
||||
<strong>
|
||||
{ deleteStatus.deleted_action_count }
|
||||
</strong>{ ' ' }
|
||||
actions.
|
||||
</>
|
||||
) }
|
||||
{ errorMessage && (
|
||||
<>
|
||||
<strong>Error: </strong>
|
||||
{ errorMessage }
|
||||
</>
|
||||
) }
|
||||
</span>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export { AdminNotes } from './admin-notes.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: <Options />,
|
||||
},
|
||||
{
|
||||
name: 'admin-notes',
|
||||
title: 'Admin notes',
|
||||
content: <AdminNotes />,
|
||||
},
|
||||
{
|
||||
name: 'tools',
|
||||
title: 'Tools',
|
||||
content: <Tools />,
|
||||
},
|
||||
{
|
||||
name: 'experiments',
|
||||
title: 'Experiments',
|
||||
content: <Experiments />,
|
||||
},
|
||||
{
|
||||
name: 'features',
|
||||
title: 'Features',
|
||||
content: <Features />,
|
||||
},
|
||||
{
|
||||
name: 'rest-api-filters',
|
||||
title: 'REST API FIlters',
|
||||
content: <RestAPIFilters />,
|
||||
},
|
||||
] );
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<div className="wrap">
|
||||
<h1>WooCommerce Admin Test Helper</h1>
|
||||
<TabPanel
|
||||
className="woocommerce-admin-test-helper__main-tab-panel"
|
||||
activeClass="active-tab"
|
||||
tabs={ tabs }
|
||||
initialTabName={ tabs[ 0 ].name }
|
||||
>
|
||||
{ ( tab ) => (
|
||||
<>
|
||||
{ tab.content }
|
||||
{ applyFilters(
|
||||
`woocommerce_admin_test_helper_tab_${ tab.name }`,
|
||||
[]
|
||||
) }
|
||||
</>
|
||||
) }
|
||||
</TabPanel>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export { App } from './app';
|
|
@ -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 (
|
||||
<div className="manual-input">
|
||||
<div className="description">
|
||||
Don't see an experiment you want to test? Add it manually.
|
||||
</div>
|
||||
<input type="text" onChange={ getInputValue } />
|
||||
<select value={ variation } onChange={ getVariationInput }>
|
||||
<option value="treatment">treatment</option>
|
||||
<option value="control">control</option>
|
||||
</select>
|
||||
|
||||
<Button isPrimary onClick={ AddNewExperiment }>
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDispatch( ( dispatch ) => {
|
||||
const { addExperiment } = dispatch( STORE_KEY );
|
||||
return {
|
||||
addExperiment,
|
||||
};
|
||||
} )
|
||||
)( NewExperimentForm );
|
|
@ -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;
|
|
@ -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,
|
||||
};
|
||||
}
|
|
@ -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';
|
|
@ -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,
|
||||
} );
|
|
@ -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;
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export function getExperiments( state ) {
|
||||
return state.experiments;
|
||||
}
|
|
@ -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 (
|
||||
<div id="wc-admin-test-helper-experiments">
|
||||
<h2>Experiments</h2>
|
||||
{ isTrackingEnabled === 'no' && (
|
||||
<p className="tracking-disabled">
|
||||
The following list might not be complete without tracking
|
||||
enabled. <br />
|
||||
Please visit
|
||||
<a
|
||||
target="_blank"
|
||||
href={
|
||||
wcSettings.adminUrl +
|
||||
'/admin.php?page=wc-settings&tab=advanced§ion=woocommerce_com'
|
||||
}
|
||||
rel="noreferrer"
|
||||
>
|
||||
WooCommerce → Settings → Advanced →
|
||||
Woocommerce.com
|
||||
</a>
|
||||
and check{ ' ' }
|
||||
<b>Allow usage of WooCommerce to be tracked</b>.
|
||||
</p>
|
||||
) }
|
||||
<NewExperimentForm />
|
||||
<table className="experiments wp-list-table striped table-view-list widefat">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Experiment</th>
|
||||
<th>Variation</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ experiments.map( ( { name, variation }, index ) => {
|
||||
return (
|
||||
<tr key={ index }>
|
||||
<td className="experiment-name">{ name }</td>
|
||||
<td align="center">{ variation }</td>
|
||||
<td className="actions" align="center">
|
||||
<Button
|
||||
onClick={ () => {
|
||||
toggleExperiment( name, variation );
|
||||
} }
|
||||
isPrimary
|
||||
>
|
||||
Toggle
|
||||
</Button>
|
||||
<Button
|
||||
onClick={ () => {
|
||||
deleteExperiment( name );
|
||||
} }
|
||||
className="btn btn-danger"
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
} ) }
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 );
|
|
@ -0,0 +1,7 @@
|
|||
const TYPES = {
|
||||
TOGGLE_FEATURE: 'TOGGLE_FEATURE',
|
||||
SET_FEATURES: 'SET_FEATURES',
|
||||
SET_MODIFIED_FEATURES: 'SET_MODIFIED_FEATURES',
|
||||
};
|
||||
|
||||
export default TYPES;
|
|
@ -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,
|
||||
};
|
||||
}
|
|
@ -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';
|
|
@ -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,
|
||||
} );
|
|
@ -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;
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
export function getFeatures( state ) {
|
||||
return state.features;
|
||||
}
|
||||
|
||||
export function getModifiedFeatures( state ) {
|
||||
return state.modifiedFeatures;
|
||||
}
|
|
@ -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 (
|
||||
<div id="wc-admin-test-helper-features">
|
||||
<h2>
|
||||
Features
|
||||
<Button
|
||||
disabled={ modifiedFeatures.length === 0 }
|
||||
onClick={ () => resetModifiedFeatures() }
|
||||
isSecondary
|
||||
style={ { marginLeft: '24px' } }
|
||||
>
|
||||
Reset to defaults
|
||||
</Button>
|
||||
</h2>
|
||||
<table className="features wp-list-table striped table-view-list widefat">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Feature Name</th>
|
||||
<th>Enabled?</th>
|
||||
<th>Toggle</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ Object.keys( features ).map( ( feature_name ) => {
|
||||
return (
|
||||
<tr key={ feature_name }>
|
||||
<td className="feature-name">
|
||||
{ feature_name }
|
||||
</td>
|
||||
<td>{ features[ feature_name ].toString() }</td>
|
||||
<td>
|
||||
<Button
|
||||
onClick={ () => {
|
||||
toggleFeature( feature_name );
|
||||
} }
|
||||
isPrimary
|
||||
>
|
||||
Toggle
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
} ) }
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Features;
|
|
@ -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( <App />, appRoot );
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 (
|
||||
<>
|
||||
<textarea
|
||||
className="wca-test-helper-option-editor"
|
||||
value={ value }
|
||||
onChange={ handleChange }
|
||||
></textarea>
|
||||
<Button
|
||||
className="wca-test-helper-edit-btn-save"
|
||||
isPrimary
|
||||
onClick={ handleSave }
|
||||
disabled={ props.option.isSaving === true }
|
||||
>
|
||||
{ props.option.isSaving ? 'Saving...' : 'Save' }
|
||||
</Button>
|
||||
<div className="clear"></div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
OptionEditor.propTypes = {
|
||||
option: PropTypes.object.isRequired,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default OptionEditor;
|
|
@ -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;
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export const STORE_KEY = 'wc-admin-helper/options';
|
||||
export const API_NAMESPACE = '/wc-admin-test-helper';
|
|
@ -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,
|
||||
} );
|
|
@ -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;
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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 (
|
||||
<tr>
|
||||
<td colSpan="6" align="center">
|
||||
Loading...
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
const renderTableData = () => {
|
||||
if ( options.length === 0 ) {
|
||||
return (
|
||||
<tr>
|
||||
<td colSpan="6" align="center">
|
||||
No Options Found
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<tr key={ optionId }>
|
||||
<td key={ 0 }>{ optionId }</td>
|
||||
<td key={ 1 }>{ optionName }</td>
|
||||
<td key={ 'optionValue' }>{ optionValue }</td>
|
||||
<td className="align-center" key={ 2 }>
|
||||
{ autoload }
|
||||
</td>
|
||||
<td className="align-center" key={ 3 }>
|
||||
<button
|
||||
className="button btn-danger"
|
||||
onClick={ () => deleteOptionByName( optionName ) }
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
<td className="align-center" key={ 4 }>
|
||||
<button
|
||||
className="button btn-primary"
|
||||
onClick={ () => openEditModal( optionName ) }
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
} );
|
||||
};
|
||||
|
||||
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 && (
|
||||
<Modal
|
||||
title={ editingOption.name }
|
||||
onRequestClose={ () => {
|
||||
setEditModalOpen( false );
|
||||
} }
|
||||
>
|
||||
<OptionEditor
|
||||
option={ editingOption }
|
||||
onSave={ handleSaveOption }
|
||||
></OptionEditor>
|
||||
</Modal>
|
||||
) }
|
||||
<div id="wc-admin-test-helper-options">
|
||||
{ notice.message.length > 0 && (
|
||||
<Notice
|
||||
status={ notice.status }
|
||||
onRemove={ () => {
|
||||
setNotice( { message: '' } );
|
||||
} }
|
||||
>
|
||||
{ notice.message }
|
||||
</Notice>
|
||||
) }
|
||||
<form onSubmit={ searchOption }>
|
||||
<div className="search-box">
|
||||
<label
|
||||
className="screen-reader-text"
|
||||
htmlFor="post-search-input"
|
||||
>
|
||||
Search options:
|
||||
</label>
|
||||
<input type="search" name="search" />
|
||||
<input
|
||||
type="submit"
|
||||
id="search-submit"
|
||||
className="button"
|
||||
value="Search options"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<div className="clear"></div>
|
||||
<table className="wp-list-table striped table-view-list widefat">
|
||||
<thead>
|
||||
<tr>
|
||||
<td
|
||||
className="manage-column column-thumb"
|
||||
key={ 0 }
|
||||
>
|
||||
I.D
|
||||
</td>
|
||||
<td
|
||||
className="manage-column column-thumb"
|
||||
key={ 1 }
|
||||
>
|
||||
Name
|
||||
</td>
|
||||
<td
|
||||
className="manage-column column-thumb"
|
||||
key={ 'optionValue' }
|
||||
>
|
||||
Value
|
||||
</td>
|
||||
<td
|
||||
className="manage-column column-thumb align-center"
|
||||
key={ 2 }
|
||||
>
|
||||
Autoload
|
||||
</td>
|
||||
<td
|
||||
className="manage-column column-thumb align-center"
|
||||
key={ 3 }
|
||||
>
|
||||
Delete
|
||||
</td>
|
||||
<td
|
||||
className="manage-column column-thumb align-center"
|
||||
key={ 4 }
|
||||
>
|
||||
Edit
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ isLoading ? renderLoading() : renderTableData() }
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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 );
|
|
@ -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;
|
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
* 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} 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();
|
||||
}
|
||||
}
|
|
@ -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';
|
|
@ -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,
|
||||
} );
|
|
@ -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;
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
export function getFilters( state ) {
|
||||
return state.filters;
|
||||
}
|
||||
|
||||
export function isLoading( state ) {
|
||||
return state.isLoading;
|
||||
}
|
|
@ -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 (
|
||||
<tr>
|
||||
<td colSpan="6" align="center">
|
||||
Loading...
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
const renderTableData = () => {
|
||||
if ( filters.length === 0 ) {
|
||||
return (
|
||||
<tr>
|
||||
<td colSpan="7" align="center">
|
||||
No Filters Found
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
return filters.map( ( filter, index ) => {
|
||||
// eslint-disable-next-line camelcase
|
||||
const {
|
||||
endpoint,
|
||||
dot_notation: dotNotation,
|
||||
replacement,
|
||||
enabled,
|
||||
} = filter;
|
||||
|
||||
return (
|
||||
<tr key={ index }>
|
||||
<td>{ index + 1 }</td>
|
||||
<td>{ endpoint }</td>
|
||||
<td key={ 'optionValue' }>{ dotNotation }</td>
|
||||
<td className="align-center">{ replacement + '' }</td>
|
||||
<td className="align-center">{ enabled + '' }</td>
|
||||
<td className="align-center">
|
||||
<button
|
||||
className="button btn-primary"
|
||||
onClick={ () => toggleFilter( index ) }
|
||||
>
|
||||
Toggle
|
||||
</button>
|
||||
</td>
|
||||
<td className="align-center">
|
||||
<button
|
||||
className="button btn-danger"
|
||||
onClick={ () => deleteFilter( index ) }
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
} );
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{ isNewModalOpen && (
|
||||
<Modal
|
||||
title={ 'New Filter' }
|
||||
onRequestClose={ () => {
|
||||
setNewModalOpen( false );
|
||||
} }
|
||||
>
|
||||
<form
|
||||
className="rest-api-filter-new-form"
|
||||
onSubmit={ submitAddForm }
|
||||
>
|
||||
<div className="grid">
|
||||
<label htmlFor="endpoint">Endpoint</label>
|
||||
<input type="text" name="endpoint" />
|
||||
<label htmlFor="jsonPath">Dot Notation</label>
|
||||
<input type="text" name="dotNotation" />
|
||||
<label htmlFor="replacement">Replacement </label>
|
||||
<input type="text" name="replacement" />
|
||||
</div>
|
||||
<input
|
||||
type="submit"
|
||||
value="Create New Filter"
|
||||
className="button btn-new"
|
||||
/>
|
||||
</form>
|
||||
</Modal>
|
||||
) }
|
||||
<div id="wc-admin-test-helper-rest-api-filters">
|
||||
<input
|
||||
type="button"
|
||||
className="button btn-primary btn-new"
|
||||
value="New Filter"
|
||||
onClick={ () => setNewModalOpen( true ) }
|
||||
/>
|
||||
<br />
|
||||
<br />
|
||||
<table className="wp-list-table striped table-view-list widefat">
|
||||
<thead>
|
||||
<tr>
|
||||
<td className="manage-column column-thumb">I.D</td>
|
||||
<td className="manage-column column-thumb">
|
||||
Endpoint
|
||||
</td>
|
||||
<td className="manage-column column-thumb">
|
||||
Dot Notation
|
||||
</td>
|
||||
<td className="manage-column column-thumb align-center">
|
||||
Replacement
|
||||
</td>
|
||||
<td className="manage-column column-thumb align-center">
|
||||
Enabled
|
||||
</td>
|
||||
<td className="manage-column column-thumb align-center">
|
||||
Toggle
|
||||
</td>
|
||||
<td className="manage-column column-thumb align-center"></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ isLoading ? renderLoading() : renderTableData() }
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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 );
|
|
@ -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.
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* 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 <div className="disable-wc-email">{ getEmailStatus() }</div>;
|
||||
};
|
|
@ -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: <TriggerCronJob />,
|
||||
action: TRIGGER_CRON_ACTION_NAME,
|
||||
},
|
||||
{
|
||||
command: 'Disable WC emails',
|
||||
description: <DisableEmail />,
|
||||
action: 'runDisableEmail',
|
||||
},
|
||||
{
|
||||
command: 'Run version update callbacks',
|
||||
description: <TriggerUpdateCallbacks />,
|
||||
action: TRIGGER_UPDATE_CALLBACKS_ACTION_NAME,
|
||||
},
|
||||
];
|
|
@ -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 (
|
||||
<div className="trigger-cron-job">
|
||||
{ ! cronList ? (
|
||||
<p>Loading ...</p>
|
||||
) : (
|
||||
<SelectControl
|
||||
label="Select cron job to run"
|
||||
onChange={ onCronChange }
|
||||
labelPosition="side"
|
||||
options={ getOptions() }
|
||||
/>
|
||||
) }
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -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 (
|
||||
<div className="trigger-cron-job">
|
||||
{ ! dbUpdateVersions ? (
|
||||
<p>Loading ...</p>
|
||||
) : (
|
||||
<SelectControl
|
||||
label="Select a version to run"
|
||||
onChange={ onCronChange }
|
||||
labelPosition="side"
|
||||
options={ getOptions().reverse() }
|
||||
/>
|
||||
) }
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -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;
|
|
@ -0,0 +1,212 @@
|
|||
/**
|
||||
* 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 );
|
||||
} );
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export const STORE_KEY = 'wc-admin-helper/tools';
|
||||
export const API_NAMESPACE = '/wc-admin-test-helper';
|
|
@ -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,
|
||||
} );
|
|
@ -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;
|
|
@ -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 );
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue