Merge pull request #34 from woocommerce/add-features-helper

Add features tab
This commit is contained in:
louwie17 2022-03-21 15:50:23 -03:00 committed by GitHub
commit b1d0940db3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 324 additions and 14 deletions

View File

@ -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' );

59
api/features/features.php Normal file
View File

@ -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();
}

View File

@ -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;
} );

View File

@ -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: <Experiments />,
},
{
name: 'features',
title: 'Features',
content: <Features />,
},
]);
export function App() {

View File

@ -9,42 +9,42 @@ import { apiFetch } from '@wordpress/data-controls';
import TYPES from './action-types';
import { EXPERIMENT_NAME_PREFIX, TRANSIENT_NAME_PREFIX } from './constants';
function toggleFrontendExperiment( experimentName, newVariation ) {
function toggleFrontendExperiment(experimentName, newVariation) {
const storageItem = JSON.parse(
window.localStorage.getItem( EXPERIMENT_NAME_PREFIX + experimentName )
window.localStorage.getItem(EXPERIMENT_NAME_PREFIX + experimentName)
);
storageItem.variationName = newVariation;
window.localStorage.setItem(
EXPERIMENT_NAME_PREFIX + experimentName,
JSON.stringify( storageItem )
JSON.stringify(storageItem)
);
}
function* toggleBackendExperiment( experimentName, newVariation ) {
function* toggleBackendExperiment(experimentName, newVariation) {
try {
const payload = {};
payload[ TRANSIENT_NAME_PREFIX + experimentName ] = newVariation;
yield apiFetch( {
payload[TRANSIENT_NAME_PREFIX + experimentName] = newVariation;
yield apiFetch({
method: 'POST',
path: '/wc-admin/options',
headers: { 'content-type': 'application/json' },
body: JSON.stringify( payload ),
} );
} catch ( error ) {
body: JSON.stringify(payload),
});
} catch (error) {
throw new Error();
}
}
export function* toggleExperiment( experimentName, currentVariation, source ) {
export function* toggleExperiment(experimentName, currentVariation, source) {
const newVariation =
currentVariation === 'control' ? 'treatment' : 'control';
if ( source === 'frontend' ) {
toggleFrontendExperiment( experimentName, newVariation );
if (source === 'frontend') {
toggleFrontendExperiment(experimentName, newVariation);
} else {
yield toggleBackendExperiment( experimentName, newVariation );
yield toggleBackendExperiment(experimentName, newVariation);
}
return {
@ -55,7 +55,7 @@ export function* toggleExperiment( experimentName, currentVariation, source ) {
};
}
export function setExperiments( experiments ) {
export function setExperiments(experiments) {
return {
type: TYPES.SET_EXPERIMENTS,
experiments,

View File

@ -0,0 +1,7 @@
const TYPES = {
TOGGLE_FEATURE: 'TOGGLE_FEATURE',
SET_FEATURES: 'SET_FEATURES',
SET_MODIFIED_FEATURES: 'SET_MODIFIED_FEATURES',
};
export default TYPES;

View File

@ -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,
};
}

View File

@ -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';

View File

@ -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,
} );

View File

@ -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;

View File

@ -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();
}
}

View File

@ -0,0 +1,7 @@
export function getFeatures( state ) {
return state.features;
}
export function getModifiedFeatures( state ) {
return state.modifiedFeatures;
}

72
src/features/index.js Normal file
View File

@ -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;