From 46cba233139c6a4c96d7f6c8ec23a69b1771948b Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Tue, 15 Mar 2022 11:09:40 -0300 Subject: [PATCH] A feature tab to toggle features --- api/api.php | 1 + api/features/features.php | 59 +++++++++++++++++++++++++++ plugin.php | 10 +++++ src/app/app.js | 6 +++ src/features/data/action-types.js | 7 ++++ src/features/data/actions.js | 51 ++++++++++++++++++++++++ src/features/data/constants.js | 3 ++ src/features/data/index.js | 22 +++++++++++ src/features/data/reducer.js | 28 +++++++++++++ src/features/data/resolvers.js | 34 ++++++++++++++++ src/features/data/selectors.js | 7 ++++ src/features/index.js | 66 +++++++++++++++++++++++++++++++ 12 files changed, 294 insertions(+) create mode 100644 api/features/features.php create mode 100644 src/features/data/action-types.js create mode 100644 src/features/data/actions.js create mode 100644 src/features/data/constants.js create mode 100644 src/features/data/index.js create mode 100644 src/features/data/reducer.js create mode 100644 src/features/data/resolvers.js create mode 100644 src/features/data/selectors.js create mode 100644 src/features/index.js diff --git a/api/api.php b/api/api.php index 9a3a02173bf..cab00d3441b 100644 --- a/api/api.php +++ b/api/api.php @@ -36,3 +36,4 @@ 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' ); diff --git a/api/features/features.php b/api/features/features.php new file mode 100644 index 00000000000..f19db0cb94f --- /dev/null +++ b/api/features/features.php @@ -0,0 +1,59 @@ +[a-z0-9_\-]+)/toggle', + 'toggle_feature', + array( + 'methods' => 'POST', + ) +); + +register_woocommerce_admin_test_helper_rest_route( + '/features', + 'get_features', + array( + 'methods' => 'GET', + ) +); + +register_woocommerce_admin_test_helper_rest_route( + '/features/reset', + 'reset_features', + array( + 'methods' => 'POST', + ) +); + +function toggle_feature( $request ) { + $features = get_features(); + $custom_feature_values = get_option( OPTION_NAME_PREFIX, array() ); + $feature_name = $request->get_param( 'feature_name' ); + + if ( ! isset( $features[$feature_name ]) ) { + return new WP_REST_Response( $features, 204 ); + } + + if ( isset( $custom_feature_values[$feature_name] ) ) { + unset( $custom_feature_values[$feature_name] ); + } else { + $custom_feature_values[$feature_name] = ! $features[ $feature_name ]; + } + + update_option(OPTION_NAME_PREFIX, $custom_feature_values ); + return new WP_REST_Response( get_features(), 200 ); +} + +function reset_features() { + delete_option( OPTION_NAME_PREFIX ); + return new WP_REST_Response( get_features(), 200 ); +} + +function get_features() { + if ( function_exists( 'wc_admin_get_feature_config' ) ) { + return apply_filters( 'woocommerce_admin_get_feature_config', wc_admin_get_feature_config() ); + } + return array(); +} diff --git a/plugin.php b/plugin.php index 04ba8574c2f..0d01e8f77d8 100644 --- a/plugin.php +++ b/plugin.php @@ -14,3 +14,13 @@ add_action( 'admin_menu', function() { 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; +} ); \ No newline at end of file diff --git a/src/app/app.js b/src/app/app.js index 69d70987a8d..acc8790b431 100644 --- a/src/app/app.js +++ b/src/app/app.js @@ -11,6 +11,7 @@ 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'; const tabs = applyFilters('woocommerce_admin_test_helper_tabs', [ { @@ -33,6 +34,11 @@ const tabs = applyFilters('woocommerce_admin_test_helper_tabs', [ title: 'Experiments', content: , }, + { + name: 'features', + title: 'Features', + content: , + }, ]); export function App() { diff --git a/src/features/data/action-types.js b/src/features/data/action-types.js new file mode 100644 index 00000000000..1e6a4d8a3bf --- /dev/null +++ b/src/features/data/action-types.js @@ -0,0 +1,7 @@ +const TYPES = { + TOGGLE_FEATURE: 'TOGGLE_FEATURE', + SET_FEATURES: 'SET_FEATURES', + SET_MODIFIED_FEATURES: 'SET_MODIFIED_FEATURES', +}; + +export default TYPES; diff --git a/src/features/data/actions.js b/src/features/data/actions.js new file mode 100644 index 00000000000..1746556042f --- /dev/null +++ b/src/features/data/actions.js @@ -0,0 +1,51 @@ +/** + * External dependencies + */ +import { apiFetch } from '@wordpress/data-controls'; + +/** + * Internal dependencies + */ +import TYPES from './action-types'; +import { API_NAMESPACE } 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' }, + }); + return yield setFeatures(response); + } catch (error) { + throw new Error(); + } +} + +export function setFeatures(features) { + return { + type: TYPES.SET_FEATURES, + features, + }; +} + +export function setModifiedFeatures(modifiedFeatures) { + return { + type: TYPES.SET_MODIFIED_FEATURES, + modifiedFeatures, + }; +} diff --git a/src/features/data/constants.js b/src/features/data/constants.js new file mode 100644 index 00000000000..c9da14951e5 --- /dev/null +++ b/src/features/data/constants.js @@ -0,0 +1,3 @@ +export const STORE_KEY = 'wc-admin-helper/features'; +export const OPTION_NAME_PREFIX = 'wc_admin_helper_feature_values'; +export const API_NAMESPACE = '/wc-admin-test-helper'; diff --git a/src/features/data/index.js b/src/features/data/index.js new file mode 100644 index 00000000000..e476479ea88 --- /dev/null +++ b/src/features/data/index.js @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import { registerStore } from '@wordpress/data'; +import { controls } from '@wordpress/data-controls'; + +/** + * Internal dependencies + */ +import * as actions from './actions'; +import * as resolvers from './resolvers'; +import * as selectors from './selectors'; +import reducer from './reducer'; +import { STORE_KEY } from './constants'; + +export default registerStore( STORE_KEY, { + actions, + selectors, + resolvers, + controls, + reducer, +} ); diff --git a/src/features/data/reducer.js b/src/features/data/reducer.js new file mode 100644 index 00000000000..1bc4844476c --- /dev/null +++ b/src/features/data/reducer.js @@ -0,0 +1,28 @@ +/** + * Internal dependencies + */ +import TYPES from './action-types'; + +const DEFAULT_STATE = { + features: {}, + modifiedFeatures: [], +}; + +const reducer = (state = DEFAULT_STATE, action) => { + switch (action.type) { + case TYPES.SET_MODIFIED_FEATURES: + return { + ...state, + modifiedFeatures: action.modifiedFeatures, + }; + case TYPES.SET_FEATURES: + return { + ...state, + features: action.features, + }; + default: + return state; + } +}; + +export default reducer; diff --git a/src/features/data/resolvers.js b/src/features/data/resolvers.js new file mode 100644 index 00000000000..9b705333922 --- /dev/null +++ b/src/features/data/resolvers.js @@ -0,0 +1,34 @@ +/** + * 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: `${API_NAMESPACE}/options?search=` + OPTION_NAME_PREFIX, + }); + + yield setModifiedFeatures(Object.keys(response)); + } catch (error) { + throw new Error(); + } +} + +export function* getFeatures() { + try { + const response = yield apiFetch({ + path: `${API_NAMESPACE}/features`, + }); + + yield setFeatures(response); + } catch (error) { + throw new Error(); + } +} diff --git a/src/features/data/selectors.js b/src/features/data/selectors.js new file mode 100644 index 00000000000..73bbacb2f49 --- /dev/null +++ b/src/features/data/selectors.js @@ -0,0 +1,7 @@ +export function getFeatures(state) { + return state.features; +} + +export function getModifiedFeatures(state) { + return state.modifiedFeatures; +} diff --git a/src/features/index.js b/src/features/index.js new file mode 100644 index 00000000000..784815b181b --- /dev/null +++ b/src/features/index.js @@ -0,0 +1,66 @@ +/** + * External dependencies + */ +import { useDispatch, useSelect } from '@wordpress/data'; +import { Button } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { STORE_KEY } from './data/constants'; +import './data'; + +function Features() { + const { features = {}, modifiedFeatures = [] } = useSelect((select) => { + const { getFeatures, getModifiedFeatures } = select(STORE_KEY); + return { + features: getFeatures(), + modifiedFeatures: getModifiedFeatures(), + }; + }); + + const { toggleFeature, resetModifiedFeatures } = useDispatch(STORE_KEY); + + return ( +
+

Features

+ + + + + + + + + + + {Object.keys(features).map((feature_name) => { + return ( + + + + + + ); + })} + +
Feature NameEnabled?Toggle
{feature_name}{features[feature_name].toString()} + +
+
+ ); +} + +export default Features;