diff --git a/api/api.php b/api/api.php index 2e11827288e..f1ff2ec9023 100644 --- a/api/api.php +++ b/api/api.php @@ -24,3 +24,4 @@ function register_woocommerce_admin_test_helper_rest_route( $route, $callback ) require( 'admin-notes/delete-all-notes.php' ); require( 'admin-notes/add-note.php' ); require( 'tools/trigger-wca-install.php' ); +require( 'options/rest-api.php' ); diff --git a/api/options/rest-api.php b/api/options/rest-api.php new file mode 100644 index 00000000000..2faf2a8f780 --- /dev/null +++ b/api/options/rest-api.php @@ -0,0 +1,106 @@ +namespace, + '/options', + array( + 'methods' => 'GET', + 'callback' => array( $this, 'get_items' ), + 'args' => array( + 'per_page' => $this->get_collection_params()['per_page'], + ), + 'permission_callback' => function( $request ) { + return true; + }, + ) + ); + + register_rest_route( + $this->namespace, + '/options/(?P\d+)', + array( + 'methods' => 'DELETE', + 'callback' => array( $this, 'delete_item' ), + 'args' => array( + 'option_id' => array( + 'type' => 'integer', + 'sanitize_callback' => 'absint', + ), + ), + 'permission_callback' => function( $request ) { + return true; + }, + ) + ); + } + + public function delete_item( $request ) { + global $wpdb; + $option_id = $request->get_param( 'option_id' ); + $query = $wpdb->prepare( "delete from {$wpdb->prefix}options where option_id = %d", $option_id ); + $wpdb->query( $query ); + + return new WP_REST_RESPONSE( null, 204 ); + } + + public function get_items( $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, 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 ); + } + + /** + * Get the query params for collections + * + * @return array + */ + public function get_collection_params() { + return 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', + ), + ); + } +} + +add_action( + 'rest_api_init', + function() { + ( new Options_Rest_Api() )->register_routes(); + } +); diff --git a/package-lock.json b/package-lock.json index 439cf07c44d..08031126a41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2973,6 +2973,17 @@ "use-memo-one": "^1.1.1" } }, + "@wordpress/data-controls": { + "version": "1.20.7", + "resolved": "https://registry.npmjs.org/@wordpress/data-controls/-/data-controls-1.20.7.tgz", + "integrity": "sha512-81/Cbk7PXAKOf0ButPqSDUdUntu5ltQVhvdfEBbFOwUxPYHiRNlP05mFuGHSqR2yJPcZToW7g1qFj2UyEK66SA==", + "requires": { + "@babel/runtime": "^7.12.5", + "@wordpress/api-fetch": "^3.21.5", + "@wordpress/data": "^4.26.7", + "@wordpress/deprecated": "^2.11.1" + } + }, "@wordpress/date": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/@wordpress/date/-/date-3.13.1.tgz", @@ -3417,15 +3428,6 @@ "strip-bom": "^2.0.0" } }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, "lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", @@ -3436,30 +3438,6 @@ "yallist": "^2.1.2" } }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", @@ -3489,59 +3467,6 @@ "pinkie-promise": "^2.0.0" } }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - } - } - }, - "prettier": { - "version": "npm:wp-prettier@2.2.1-beta-1", - "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-2.2.1-beta-1.tgz", - "integrity": "sha512-+JHkqs9LC/JPp51yy1hzs3lQ7qeuWCwOcSzpQNeeY/G7oSpnF61vxt7hRh87zNRTr6ob2ndy0W8rVzhgrcA+Gw==", - "dev": true - }, - "puppeteer": { - "version": "npm:puppeteer-core@5.5.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-5.5.0.tgz", - "integrity": "sha512-tlA+1n+ziW/Db03hVV+bAecDKse8ihFRXYiEypBe9IlLRvOCzYFG6qrCMBYK34HO/Q/Ecjc+tvkHRAfLVH+NgQ==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "devtools-protocol": "0.0.818844", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^4.0.0", - "node-fetch": "^2.6.1", - "pkg-dir": "^4.2.0", - "progress": "^2.0.1", - "proxy-from-env": "^1.0.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" - } - }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", @@ -13820,6 +13745,12 @@ "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "dev": true }, + "prettier": { + "version": "npm:wp-prettier@2.2.1-beta-1", + "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-2.2.1-beta-1.tgz", + "integrity": "sha512-+JHkqs9LC/JPp51yy1hzs3lQ7qeuWCwOcSzpQNeeY/G7oSpnF61vxt7hRh87zNRTr6ob2ndy0W8rVzhgrcA+Gw==", + "dev": true + }, "prettier-linter-helpers": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", @@ -14011,6 +13942,86 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, + "puppeteer": { + "version": "npm:puppeteer-core@5.5.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-5.5.0.tgz", + "integrity": "sha512-tlA+1n+ziW/Db03hVV+bAecDKse8ihFRXYiEypBe9IlLRvOCzYFG6qrCMBYK34HO/Q/Ecjc+tvkHRAfLVH+NgQ==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "devtools-protocol": "0.0.818844", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^4.0.0", + "node-fetch": "^2.6.1", + "pkg-dir": "^4.2.0", + "progress": "^2.0.1", + "proxy-from-env": "^1.0.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + } + } + }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", diff --git a/package.json b/package.json index 6c1e3822560..f08b4aad5c8 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,9 @@ "@woocommerce/data": "^1.1.1", "@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" } diff --git a/src/App/app.js b/src/App/app.js index 8527f758875..d6482f7213a 100644 --- a/src/App/app.js +++ b/src/App/app.js @@ -1,17 +1,15 @@ /** - * External dependencies. + * External dependencies */ import { TabPanel } from '@wordpress/components'; -import { applyFilters, addFilter } from '@wordpress/hooks'; +import { applyFilters } from '@wordpress/hooks'; /** * Internal dependencies */ -// TODO replace this with the actual controls -// import { Options } from '../Options'; -const Options = () =>

Options

; import { AdminNotes } from '../admin-notes'; import { Tools } from '../tools'; +import { default as Options } from '../options'; const tabs = applyFilters( 'woocommerce_admin_test_helper_tabs', @@ -42,13 +40,13 @@ export function App() { className="woocommerce-admin-test-helper__main-tab-panel" activeClass="active-tab" tabs={ tabs } - initialTabName={ tabs[0].name } + initialTabName={ tabs[ 0 ].name } > { ( tab ) => ( <> { tab.content } { applyFilters( - `woocommerce_admin_test_helper_tab_${tab.name}`, + `woocommerce_admin_test_helper_tab_${ tab.name }`, [] ) } diff --git a/src/index.scss b/src/index.scss index f7a2ad29927..e55b4600ba0 100644 --- a/src/index.scss +++ b/src/index.scss @@ -10,3 +10,17 @@ color: var(--wp-admin-theme-color); font-family: monospace; } + + +#wc-admin-test-helper-options { + div.search-box { + float: right; + margin-bottom:10px; + } + + .btn-danger { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; + } +} \ No newline at end of file diff --git a/src/options/data/action-types.js b/src/options/data/action-types.js new file mode 100644 index 00000000000..92b72ab3d58 --- /dev/null +++ b/src/options/data/action-types.js @@ -0,0 +1,6 @@ +const TYPES = { + SET_OPTIONS: 'SET_OPTIONS', + DELETE_OPTION_BY_ID: 'DELETE_OPTION_BY_ID', +}; + +export default TYPES; diff --git a/src/options/data/actions.js b/src/options/data/actions.js new file mode 100644 index 00000000000..2db057d6542 --- /dev/null +++ b/src/options/data/actions.js @@ -0,0 +1,37 @@ +/** + * 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* deleteOptionById( optionId ) { + try { + yield apiFetch( { + method: 'DELETE', + path: `${ API_NAMESPACE }/options/${ optionId }`, + } ); + yield { + type: TYPES.DELETE_OPTION_BY_ID, + optionId, + }; + } catch { + throw new Error(); + } +} diff --git a/src/options/data/constants.js b/src/options/data/constants.js new file mode 100644 index 00000000000..92727b4c95d --- /dev/null +++ b/src/options/data/constants.js @@ -0,0 +1,2 @@ +export const STORE_KEY = 'wc-admin-helper/options'; +export const API_NAMESPACE = '/wc-admin-test-helper/v1'; diff --git a/src/options/data/index.js b/src/options/data/index.js new file mode 100644 index 00000000000..e476479ea88 --- /dev/null +++ b/src/options/data/index.js @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import { registerStore } from '@wordpress/data'; +import { controls } from '@wordpress/data-controls'; + +/** + * Internal dependencies + */ +import * as actions from './actions'; +import * as resolvers from './resolvers'; +import * as selectors from './selectors'; +import reducer from './reducer'; +import { STORE_KEY } from './constants'; + +export default registerStore( STORE_KEY, { + actions, + selectors, + resolvers, + controls, + reducer, +} ); diff --git a/src/options/data/reducer.js b/src/options/data/reducer.js new file mode 100644 index 00000000000..805cdcc13d0 --- /dev/null +++ b/src/options/data/reducer.js @@ -0,0 +1,30 @@ +/** + * Internal dependencies + */ +import TYPES from './action-types'; + +const DEFAULT_STATE = { + options: [], +}; + +const reducer = ( state = DEFAULT_STATE, action ) => { + switch ( action.type ) { + case TYPES.SET_OPTIONS: + return { + ...state, + options: action.options, + }; + + case TYPES.DELETE_OPTION_BY_ID: + return { + ...state, + options: state.options.filter( + ( item ) => item.option_id !== action.optionId + ), + }; + default: + return state; + } +}; + +export default reducer; diff --git a/src/options/data/resolvers.js b/src/options/data/resolvers.js new file mode 100644 index 00000000000..24ce37baf60 --- /dev/null +++ b/src/options/data/resolvers.js @@ -0,0 +1,27 @@ +/** + * External dependencies + */ +import { apiFetch } from '@wordpress/data-controls'; + +/** + * Internal dependencies + */ +import { API_NAMESPACE } from './constants'; +import { setOptions } from './actions'; + +export function* getOptions( search ) { + let path = `${ API_NAMESPACE }/options?`; + if ( search ) { + path += `search=${ search }`; + } + console.log( path ); + + try { + const response = yield apiFetch( { + path, + } ); + yield setOptions( response ); + } catch ( error ) { + throw new Error(); + } +} diff --git a/src/options/data/selectors.js b/src/options/data/selectors.js new file mode 100644 index 00000000000..2d9024eed82 --- /dev/null +++ b/src/options/data/selectors.js @@ -0,0 +1,3 @@ +export function getOptions( state ) { + return state.options; +} diff --git a/src/options/index.js b/src/options/index.js new file mode 100644 index 00000000000..923cd49bc49 --- /dev/null +++ b/src/options/index.js @@ -0,0 +1,122 @@ +/** + * External dependencies + */ +import { withDispatch, withSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import { STORE_KEY } from './data/constants'; +import './data'; + +function Options( { + options, + getOptions, + deleteOptionById, + invalidateResolutionForStoreSelector, +} ) { + const deleteOption = function ( optionId ) { + // eslint-disable-next-line no-alert + if ( confirm( 'Are you sure you want to delete?' ) ) { + deleteOptionById( optionId ); + } + }; + + const renderTableData = function () { + return options.map( ( option ) => { + // eslint-disable-next-line camelcase + const { option_id, option_name, autoload } = option; + + // eslint-disable-next-line camelcase + const optionId = option_id; + // eslint-disable-next-line camelcase + const optionName = option_name; + + return ( + + { optionId } + { optionName } + { autoload } + + + + + ); + } ); + }; + + const searchOption = function ( event ) { + event.preventDefault(); + let keyword = event.target.search.value; + if ( keyword === '' ) { + keyword = undefined; + } + getOptions( keyword ); + + // force invlidation of the cached selector resolvers + invalidateResolutionForStoreSelector( 'getOptions' ); + }; + + return ( +
+
+
+ + + +
+
+
+ + + + + + + + + + { renderTableData() } +
+ I.D + + Name + + Autoload + + Delete +
+
+ ); +} + +export default compose( + withSelect( ( select ) => { + const { getOptions } = select( STORE_KEY ); + const options = getOptions(); + return { options, getOptions }; + } ), + withDispatch( ( dispatch ) => { + const { + deleteOptionById, + invalidateResolutionForStoreSelector, + } = dispatch( STORE_KEY ); + return { deleteOptionById, invalidateResolutionForStoreSelector }; + } ) +)( Options ); diff --git a/woocommerce-admin-test-helper.php b/woocommerce-admin-test-helper.php index ef5b8299df8..c1fdc71b94d 100644 --- a/woocommerce-admin-test-helper.php +++ b/woocommerce-admin-test-helper.php @@ -50,10 +50,14 @@ function add_extension_register_script() { ), $css_file_version ); + wp_enqueue_style( 'woocommerce-admin-test-helper' ); + + wp_enqueue_style('woocommerce-styles', '/wp-content/plugins/woocommerce-admin/dist/components/style.css'); + } add_action( 'admin_enqueue_scripts', 'add_extension_register_script' ); // Load the plugin -require( 'plugin.php' ); +require( 'plugin.php' ); \ No newline at end of file