Merge pull request #27 from woocommerce/feature/26-add-tool-to-manage-experiment

Add Experiments -- helps to toggle experiment variation
This commit is contained in:
Moon 2022-02-22 20:12:41 -08:00 committed by GitHub
commit 620ec4dfa6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 332 additions and 104 deletions

6
.eslintignore Normal file
View File

@ -0,0 +1,6 @@
bin/*
!.eslintrc.js
build
build-module
node_modules
vendor

207
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "woocommerce-admin-test-helper",
"version": "0.4.0",
"version": "0.6.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -2521,9 +2521,9 @@
"dev": true
},
"@types/yauzl": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz",
"integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==",
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz",
"integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==",
"dev": true,
"optional": true,
"requires": {
@ -3434,15 +3434,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",
@ -3453,30 +3444,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",
@ -3506,59 +3473,12 @@
"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",
@ -12289,11 +12209,38 @@
"dev": true
},
"node-fetch": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
"dev": true,
"requires": {
"whatwg-url": "^5.0.0"
},
"dependencies": {
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
"dev": true
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=",
"dev": true
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"dev": true,
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
}
}
},
"node-int64": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@ -13838,9 +13785,9 @@
"dev": true
},
"prettier": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz",
"integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==",
"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": {
@ -14034,6 +13981,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",

View File

@ -26,7 +26,7 @@
"@woocommerce/eslint-plugin": "1.1.0",
"@wordpress/prettier-config": "^1.0.1",
"@wordpress/scripts": "^13.0.3",
"prettier": "^2.2.1"
"prettier": "npm:wp-prettier@^2.2.1-beta-1"
},
"dependencies": {
"@woocommerce/data": "^1.1.1",

View File

@ -10,6 +10,7 @@ import { applyFilters } from '@wordpress/hooks';
import { AdminNotes } from '../admin-notes';
import { default as Tools } from '../tools';
import { default as Options } from '../options';
import { default as Experiments } from '../experiments';
const tabs = applyFilters('woocommerce_admin_test_helper_tabs', [
{
@ -27,6 +28,11 @@ const tabs = applyFilters( 'woocommerce_admin_test_helper_tabs', [
title: 'Tools',
content: <Tools />,
},
{
name: 'experiments',
title: 'Experiments',
content: <Experiments />,
},
]);
export function App() {

View File

@ -0,0 +1,6 @@
const TYPES = {
TOGGLE_EXPERIMENT: 'TOGGLE_EXPERIMENT',
SET_EXPERIMENTS: 'SET_EXPERIMENTS',
};
export default TYPES;

View File

@ -0,0 +1,34 @@
/**
* Internal dependencies
*/
import TYPES from './action-types';
import { EXPERIMENT_NAME_PREFIX } from './constants';
export function toggleExperiment( experimentName ) {
const storageItem = JSON.parse(
window.localStorage.getItem( EXPERIMENT_NAME_PREFIX + experimentName )
);
const newVariation =
storageItem.variationName === 'control' ? 'treatment' : 'control';
storageItem.variationName = newVariation;
window.localStorage.setItem(
EXPERIMENT_NAME_PREFIX + experimentName,
JSON.stringify( storageItem )
);
return {
type: TYPES.TOGGLE_EXPERIMENT,
experimentName,
newVariation,
};
}
export function setExperiments( experiments ) {
return {
type: TYPES.SET_EXPERIMENTS,
experiments,
};
}

View File

@ -0,0 +1,2 @@
export const STORE_KEY = 'wc-admin-helper/experiments';
export const EXPERIMENT_NAME_PREFIX = 'explat-experiment--';

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,33 @@
/**
* Internal dependencies
*/
import TYPES from './action-types';
const DEFAULT_STATE = {
experiments: [],
};
const reducer = ( state = DEFAULT_STATE, action ) => {
switch ( action.type ) {
case TYPES.TOGGLE_EXPERIMENT:
return {
...state,
experiments: state.experiments.map( ( experiment ) => ( {
...experiment,
variation:
experiment.name === action.experimentName
? action.newVariation
: experiment.variation,
} ) ),
};
case TYPES.SET_EXPERIMENTS:
return {
...state,
experiments: action.experiments,
};
default:
return state;
}
};
export default reducer;

View File

@ -0,0 +1,24 @@
/**
* Internal dependencies
*/
import { setExperiments } from './actions';
import { EXPERIMENT_NAME_PREFIX } from './constants';
export function* getExperiments() {
const storageItems = Object.entries( { ...window.localStorage } ).filter(
( item ) => {
return item[ 0 ].indexOf( EXPERIMENT_NAME_PREFIX ) === 0;
}
);
const experiments = storageItems.map( ( storageItem ) => {
const [ key, value ] = storageItem;
const objectValue = JSON.parse( value );
return {
name: key.replace( EXPERIMENT_NAME_PREFIX, '' ),
variation: objectValue.variationName || 'control',
};
} );
yield setExperiments( experiments );
}

View File

@ -0,0 +1,3 @@
export function getExperiments( state ) {
return state.experiments;
}

65
src/experiments/index.js Normal file
View File

@ -0,0 +1,65 @@
/**
* External dependencies
*/
import { withDispatch, withSelect } from '@wordpress/data';
import { compose } from '@wordpress/compose';
import { Button } from '@wordpress/components';
/**
* Internal dependencies
*/
import { STORE_KEY } from './data/constants';
import './data';
function Experiments( { experiments, toggleExperiment } ) {
return (
<div id="wc-admin-test-helper-experiments">
<h2>Experiments</h2>
<table className="experiments wp-list-table striped table-view-list widefat">
<thead>
<tr>
<th>Experiment</th>
<th>Variation</th>
<th>Toggle</th>
</tr>
</thead>
<tbody>
{ experiments.map( ( { name, variation }, index ) => {
return (
<tr key={ index }>
<td className="experiment-name">{ name }</td>
<td align="center">{ variation }</td>
<td align="center">
<Button
onClick={ () => {
toggleExperiment( name );
} }
isPrimary
>
Toggle
</Button>
</td>
</tr>
);
} ) }
</tbody>
</table>
</div>
);
}
export default compose(
withSelect( ( select ) => {
const { getExperiments } = select( STORE_KEY );
return {
experiments: getExperiments(),
};
} ),
withDispatch( ( dispatch ) => {
const { toggleExperiment } = dispatch( STORE_KEY );
return {
toggleExperiment,
};
} )
)( Experiments );

View File

@ -51,8 +51,8 @@
float: right;
}
#wc-admin-test-helper-tools {
table.tools {
#wc-admin-test-helper-tools, #wc-admin-test-helper-experiments {
table.tools, table.experiments {
thead th {
text-align: center;
}