Merge pull request #46 from woocommerce/update/allow-manual-experiment-input
Allow adding a new experiment manually
This commit is contained in:
commit
69f89dc11c
|
@ -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 );
|
|
@ -1,6 +1,8 @@
|
||||||
const TYPES = {
|
const TYPES = {
|
||||||
TOGGLE_EXPERIMENT: 'TOGGLE_EXPERIMENT',
|
TOGGLE_EXPERIMENT: 'TOGGLE_EXPERIMENT',
|
||||||
SET_EXPERIMENTS: 'SET_EXPERIMENTS',
|
SET_EXPERIMENTS: 'SET_EXPERIMENTS',
|
||||||
|
ADD_EXPERIMENT: 'ADD_EXPERIMENT',
|
||||||
|
DELETE_EXPERIMENT: 'DELETE_EXPERIMENT',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TYPES;
|
export default TYPES;
|
||||||
|
|
|
@ -14,10 +14,18 @@ import {
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
|
||||||
function toggleFrontendExperiment( experimentName, newVariation ) {
|
function toggleFrontendExperiment( experimentName, newVariation ) {
|
||||||
const storageItem = JSON.parse(
|
let storageItem = JSON.parse(
|
||||||
window.localStorage.getItem( EXPERIMENT_NAME_PREFIX + experimentName )
|
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.variationName = newVariation;
|
||||||
storageItem.ttl = 3600;
|
storageItem.ttl = 3600;
|
||||||
|
|
||||||
|
@ -45,21 +53,17 @@ function* toggleBackendExperiment( experimentName, newVariation ) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* toggleExperiment( experimentName, currentVariation, source ) {
|
export function* toggleExperiment( experimentName, currentVariation ) {
|
||||||
const newVariation =
|
const newVariation =
|
||||||
currentVariation === 'control' ? 'treatment' : 'control';
|
currentVariation === 'control' ? 'treatment' : 'control';
|
||||||
|
|
||||||
if ( source === 'frontend' ) {
|
toggleFrontendExperiment( experimentName, newVariation );
|
||||||
toggleFrontendExperiment( experimentName, newVariation );
|
yield toggleBackendExperiment( experimentName, newVariation );
|
||||||
} else {
|
|
||||||
yield toggleBackendExperiment( experimentName, newVariation );
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: TYPES.TOGGLE_EXPERIMENT,
|
type: TYPES.TOGGLE_EXPERIMENT,
|
||||||
experimentName,
|
experimentName,
|
||||||
newVariation,
|
newVariation,
|
||||||
source,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,3 +73,33 @@ export function setExperiments( experiments ) {
|
||||||
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -9,14 +9,52 @@ const DEFAULT_STATE = {
|
||||||
|
|
||||||
const reducer = ( state = DEFAULT_STATE, action ) => {
|
const reducer = ( state = DEFAULT_STATE, action ) => {
|
||||||
switch ( action.type ) {
|
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:
|
case TYPES.TOGGLE_EXPERIMENT:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
experiments: state.experiments.map( ( experiment ) => ( {
|
experiments: state.experiments.map( ( experiment ) => ( {
|
||||||
...experiment,
|
...experiment,
|
||||||
variation:
|
variation:
|
||||||
experiment.name === action.experimentName &&
|
experiment.name === action.experimentName
|
||||||
experiment.source === action.source
|
|
||||||
? action.newVariation
|
? action.newVariation
|
||||||
: experiment.variation,
|
: experiment.variation,
|
||||||
} ) ),
|
} ) ),
|
||||||
|
|
|
@ -26,7 +26,6 @@ function getExperimentsFromFrontend() {
|
||||||
return {
|
return {
|
||||||
name: key.replace( EXPERIMENT_NAME_PREFIX, '' ),
|
name: key.replace( EXPERIMENT_NAME_PREFIX, '' ),
|
||||||
variation: objectValue.variationName || 'control',
|
variation: objectValue.variationName || 'control',
|
||||||
source: 'frontend',
|
|
||||||
};
|
};
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
@ -47,12 +46,22 @@ export function* getExperiments() {
|
||||||
experiment.option_value === 'control'
|
experiment.option_value === 'control'
|
||||||
? 'control'
|
? 'control'
|
||||||
: 'treatment',
|
: 'treatment',
|
||||||
source: 'backend',
|
|
||||||
};
|
};
|
||||||
} );
|
} );
|
||||||
yield setExperiments(
|
|
||||||
getExperimentsFromFrontend().concat( experimentsFromBackend )
|
// 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 ) {
|
} catch ( error ) {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,16 +10,19 @@ import { OPTIONS_STORE_NAME } from '@woocommerce/data';
|
||||||
*/
|
*/
|
||||||
import { STORE_KEY } from './data/constants';
|
import { STORE_KEY } from './data/constants';
|
||||||
import './data';
|
import './data';
|
||||||
|
import NewExperimentForm from './NewExperimentForm';
|
||||||
|
|
||||||
function Experiments( {
|
function Experiments( {
|
||||||
experiments,
|
experiments,
|
||||||
toggleExperiment,
|
toggleExperiment,
|
||||||
|
deleteExperiment,
|
||||||
isTrackingEnabled,
|
isTrackingEnabled,
|
||||||
isResolving,
|
isResolving,
|
||||||
} ) {
|
} ) {
|
||||||
if ( isResolving ) {
|
if ( isResolving ) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="wc-admin-test-helper-experiments">
|
<div id="wc-admin-test-helper-experiments">
|
||||||
<h2>Experiments</h2>
|
<h2>Experiments</h2>
|
||||||
|
@ -43,43 +46,42 @@ function Experiments( {
|
||||||
<b>Allow usage of WooCommerce to be tracked</b>.
|
<b>Allow usage of WooCommerce to be tracked</b>.
|
||||||
</p>
|
</p>
|
||||||
) }
|
) }
|
||||||
|
<NewExperimentForm />
|
||||||
<table className="experiments wp-list-table striped table-view-list widefat">
|
<table className="experiments wp-list-table striped table-view-list widefat">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Experiment</th>
|
<th>Experiment</th>
|
||||||
<th>Variation</th>
|
<th>Variation</th>
|
||||||
<th>Source</th>
|
<th>Actions</th>
|
||||||
<th>Toggle</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{ experiments.map(
|
{ experiments.map( ( { name, variation }, index ) => {
|
||||||
( { name, variation, source }, index ) => {
|
return (
|
||||||
return (
|
<tr key={ index }>
|
||||||
<tr key={ index }>
|
<td className="experiment-name">{ name }</td>
|
||||||
<td className="experiment-name">
|
<td align="center">{ variation }</td>
|
||||||
{ name }
|
<td className="actions" align="center">
|
||||||
</td>
|
<Button
|
||||||
<td align="center">{ variation }</td>
|
onClick={ () => {
|
||||||
<td align="center">{ source }</td>
|
toggleExperiment( name, variation );
|
||||||
<td align="center">
|
} }
|
||||||
<Button
|
isPrimary
|
||||||
onClick={ () => {
|
>
|
||||||
toggleExperiment(
|
Toggle
|
||||||
name,
|
</Button>
|
||||||
variation,
|
<Button
|
||||||
source
|
onClick={ () => {
|
||||||
);
|
deleteExperiment( name );
|
||||||
} }
|
} }
|
||||||
isPrimary
|
className="btn btn-danger"
|
||||||
>
|
>
|
||||||
Toggle
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
} ) }
|
||||||
) }
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -98,10 +100,11 @@ export default compose(
|
||||||
};
|
};
|
||||||
} ),
|
} ),
|
||||||
withDispatch( ( dispatch ) => {
|
withDispatch( ( dispatch ) => {
|
||||||
const { toggleExperiment } = dispatch( STORE_KEY );
|
const { toggleExperiment, deleteExperiment } = dispatch( STORE_KEY );
|
||||||
|
|
||||||
return {
|
return {
|
||||||
toggleExperiment,
|
toggleExperiment,
|
||||||
|
deleteExperiment,
|
||||||
};
|
};
|
||||||
} )
|
} )
|
||||||
)( Experiments );
|
)( Experiments );
|
||||||
|
|
|
@ -92,6 +92,37 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#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 {
|
#wc-admin-test-helper-rest-api-filters {
|
||||||
.btn-new {
|
.btn-new {
|
||||||
float: right;
|
float: right;
|
||||||
|
|
Loading…
Reference in New Issue