Merge pull request #34 from woocommerce/add-features-helper
Add features tab
This commit is contained in:
commit
b1d0940db3
|
@ -36,3 +36,4 @@ require( 'tools/delete-all-products.php');
|
||||||
require( 'tools/disable-wc-email.php' );
|
require( 'tools/disable-wc-email.php' );
|
||||||
require( 'tools/trigger-update-callbacks.php' );
|
require( 'tools/trigger-update-callbacks.php' );
|
||||||
require( 'tracks/tracks-debug-log.php' );
|
require( 'tracks/tracks-debug-log.php' );
|
||||||
|
require( 'features/features.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();
|
||||||
|
}
|
10
plugin.php
10
plugin.php
|
@ -14,3 +14,13 @@ add_action( 'admin_menu', function() {
|
||||||
add_action( 'wp_loaded', function() {
|
add_action( 'wp_loaded', function() {
|
||||||
require( 'api/api.php' );
|
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;
|
||||||
|
} );
|
|
@ -11,6 +11,7 @@ import { AdminNotes } from '../admin-notes';
|
||||||
import { default as Tools } from '../tools';
|
import { default as Tools } from '../tools';
|
||||||
import { default as Options } from '../options';
|
import { default as Options } from '../options';
|
||||||
import { default as Experiments } from '../experiments';
|
import { default as Experiments } from '../experiments';
|
||||||
|
import { default as Features } from '../features';
|
||||||
|
|
||||||
const tabs = applyFilters('woocommerce_admin_test_helper_tabs', [
|
const tabs = applyFilters('woocommerce_admin_test_helper_tabs', [
|
||||||
{
|
{
|
||||||
|
@ -33,6 +34,11 @@ const tabs = applyFilters('woocommerce_admin_test_helper_tabs', [
|
||||||
title: 'Experiments',
|
title: 'Experiments',
|
||||||
content: <Experiments />,
|
content: <Experiments />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'features',
|
||||||
|
title: 'Features',
|
||||||
|
content: <Features />,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
|
|
|
@ -9,42 +9,42 @@ import { apiFetch } from '@wordpress/data-controls';
|
||||||
import TYPES from './action-types';
|
import TYPES from './action-types';
|
||||||
import { EXPERIMENT_NAME_PREFIX, TRANSIENT_NAME_PREFIX } from './constants';
|
import { EXPERIMENT_NAME_PREFIX, TRANSIENT_NAME_PREFIX } from './constants';
|
||||||
|
|
||||||
function toggleFrontendExperiment( experimentName, newVariation ) {
|
function toggleFrontendExperiment(experimentName, newVariation) {
|
||||||
const storageItem = JSON.parse(
|
const storageItem = JSON.parse(
|
||||||
window.localStorage.getItem( EXPERIMENT_NAME_PREFIX + experimentName )
|
window.localStorage.getItem(EXPERIMENT_NAME_PREFIX + experimentName)
|
||||||
);
|
);
|
||||||
|
|
||||||
storageItem.variationName = newVariation;
|
storageItem.variationName = newVariation;
|
||||||
|
|
||||||
window.localStorage.setItem(
|
window.localStorage.setItem(
|
||||||
EXPERIMENT_NAME_PREFIX + experimentName,
|
EXPERIMENT_NAME_PREFIX + experimentName,
|
||||||
JSON.stringify( storageItem )
|
JSON.stringify(storageItem)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function* toggleBackendExperiment( experimentName, newVariation ) {
|
function* toggleBackendExperiment(experimentName, newVariation) {
|
||||||
try {
|
try {
|
||||||
const payload = {};
|
const payload = {};
|
||||||
payload[ TRANSIENT_NAME_PREFIX + experimentName ] = newVariation;
|
payload[TRANSIENT_NAME_PREFIX + experimentName] = newVariation;
|
||||||
yield apiFetch( {
|
yield apiFetch({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/wc-admin/options',
|
path: '/wc-admin/options',
|
||||||
headers: { 'content-type': 'application/json' },
|
headers: { 'content-type': 'application/json' },
|
||||||
body: JSON.stringify( payload ),
|
body: JSON.stringify(payload),
|
||||||
} );
|
});
|
||||||
} catch ( error ) {
|
} catch (error) {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* toggleExperiment( experimentName, currentVariation, source ) {
|
export function* toggleExperiment(experimentName, currentVariation, source) {
|
||||||
const newVariation =
|
const newVariation =
|
||||||
currentVariation === 'control' ? 'treatment' : 'control';
|
currentVariation === 'control' ? 'treatment' : 'control';
|
||||||
|
|
||||||
if ( source === 'frontend' ) {
|
if (source === 'frontend') {
|
||||||
toggleFrontendExperiment( experimentName, newVariation );
|
toggleFrontendExperiment(experimentName, newVariation);
|
||||||
} else {
|
} else {
|
||||||
yield toggleBackendExperiment( experimentName, newVariation );
|
yield toggleBackendExperiment(experimentName, newVariation);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -55,7 +55,7 @@ export function* toggleExperiment( experimentName, currentVariation, source ) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setExperiments( experiments ) {
|
export function setExperiments(experiments) {
|
||||||
return {
|
return {
|
||||||
type: TYPES.SET_EXPERIMENTS,
|
type: TYPES.SET_EXPERIMENTS,
|
||||||
experiments,
|
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;
|
Loading…
Reference in New Issue