diff --git a/frontend/src/component/common/util.js b/frontend/src/component/common/util.js index 872c4f0c6f..86f0dd7493 100644 --- a/frontend/src/component/common/util.js +++ b/frontend/src/component/common/util.js @@ -1,3 +1,5 @@ +import { weightTypes } from '../feature/variant/enums'; + const dateTimeOptions = { day: '2-digit', month: '2-digit', @@ -22,11 +24,37 @@ export const trim = value => { }; export function updateWeight(variants, totalWeight) { - const size = variants.length; - const percentage = parseInt((1 / size) * totalWeight); + const variantMetadata = variants.reduce( + ({ remainingPercentage, variableVariantCount }, variant) => { + if (variant.weight && variant.weightType === weightTypes.FIX) { + remainingPercentage -= Number(variant.weight); + } else { + variableVariantCount += 1; + } + return { + remainingPercentage, + variableVariantCount, + }; + }, + { remainingPercentage: totalWeight, variableVariantCount: 0 } + ); - variants.forEach(v => { - v.weight = percentage; + const { remainingPercentage, variableVariantCount } = variantMetadata; + + if (remainingPercentage < 0) { + throw new Error('The traffic distribution total must equal 100%'); + } + + if (!variableVariantCount) { + throw new Error('There must be atleast one variable variant'); + } + + const percentage = parseInt(remainingPercentage / variableVariantCount); + + return variants.map(variant => { + if (variant.weightType !== weightTypes.FIX) { + variant.weight = percentage; + } + return variant; }); - return variants; } diff --git a/frontend/src/component/feature/variant/__tests__/__snapshots__/update-variant-component-test.jsx.snap b/frontend/src/component/feature/variant/__tests__/__snapshots__/update-variant-component-test.jsx.snap index 491fde6295..af0fb988c9 100644 --- a/frontend/src/component/feature/variant/__tests__/__snapshots__/update-variant-component-test.jsx.snap +++ b/frontend/src/component/feature/variant/__tests__/__snapshots__/update-variant-component-test.jsx.snap @@ -37,6 +37,9 @@ exports[`renders correctly with with variants 1`] = ` Weight + + Weight Type + @@ -67,6 +70,9 @@ exports[`renders correctly with with variants 1`] = ` 3.4 % + + Variable + @@ -95,6 +101,9 @@ exports[`renders correctly with with variants 1`] = ` 3.3 % + + Variable + @@ -126,6 +135,9 @@ exports[`renders correctly with with variants 1`] = ` 3.3 % + + Fix + diff --git a/frontend/src/component/feature/variant/__tests__/update-variant-component-test.jsx b/frontend/src/component/feature/variant/__tests__/update-variant-component-test.jsx index cad73143e1..028d752b33 100644 --- a/frontend/src/component/feature/variant/__tests__/update-variant-component-test.jsx +++ b/frontend/src/component/feature/variant/__tests__/update-variant-component-test.jsx @@ -4,6 +4,7 @@ import { MemoryRouter } from 'react-router-dom'; import UpdateVariant from './../update-variant-component'; import renderer from 'react-test-renderer'; import { UPDATE_FEATURE } from '../../../../permissions'; +import { weightTypes } from '../enums'; jest.mock('react-mdl'); @@ -72,6 +73,7 @@ test('renders correctly with with variants', () => { { name: 'orange', weight: 33, + weightType: weightTypes.FIX, payload: { type: 'string', value: '{"color": "blue", "animated": false}', diff --git a/frontend/src/component/feature/variant/add-variant.jsx b/frontend/src/component/feature/variant/add-variant.jsx index bf63b306c8..20d75c3d38 100644 --- a/frontend/src/component/feature/variant/add-variant.jsx +++ b/frontend/src/component/feature/variant/add-variant.jsx @@ -1,9 +1,11 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import Modal from 'react-modal'; -import { Button, Textfield, DialogActions, Grid, Cell, Icon } from 'react-mdl'; +import { Button, Textfield, DialogActions, Grid, Cell, Icon, Switch } from 'react-mdl'; +import styles from './variant.scss'; import MySelect from '../../common/select'; import { trim } from '../form/util'; +import { weightTypes } from './enums'; import OverrideConfig from './override-config'; Modal.setAppElement('#app'); @@ -46,7 +48,11 @@ function AddVariant({ showDialog, closeDialog, save, validateName, editVariant, const clear = () => { if (editVariant) { - setData({ name: editVariant.name }); + setData({ + name: editVariant.name, + weight: editVariant.weight / 10, + weightType: editVariant.weightType || weightTypes.VARIABLE, + }); if (editVariant.payload) { setPayload(editVariant.payload); } @@ -67,11 +73,20 @@ function AddVariant({ showDialog, closeDialog, save, validateName, editVariant, clear(); }, [editVariant]); - const setName = e => { - e.preventDefault(); + const setVariantValue = e => { + const { name, value } = e.target; setData({ ...data, - [e.target.name]: trim(e.target.value), + [name]: trim(value), + }); + }; + + const setVariantWeightType = e => { + const { checked, name } = e.target; + const weightType = checked ? weightTypes.FIX : weightTypes.VARIABLE; + setData({ + ...data, + [name]: weightType, }); }; @@ -88,6 +103,8 @@ function AddVariant({ showDialog, closeDialog, save, validateName, editVariant, try { const variant = { name: data.name, + weight: data.weight * 10, + weightType: data.weightType, payload: payload.value ? payload : undefined, overrides: overrides .map(o => ({ @@ -100,7 +117,7 @@ function AddVariant({ showDialog, closeDialog, save, validateName, editVariant, clear(); closeDialog(); } catch (error) { - const msg = error.error || 'Could not add variant'; + const msg = error.message || 'Could not add variant'; setError({ general: msg }); } }; @@ -152,6 +169,8 @@ function AddVariant({ showDialog, closeDialog, save, validateName, editVariant, setOverrides([...overrides, ...[{ contextName: 'userId', values: [] }]]); }; + const isFixWeight = data.weightType === weightTypes.FIX; + return (

{title}

@@ -167,10 +186,33 @@ function AddVariant({ showDialog, closeDialog, save, validateName, editVariant, value={data.name} error={error.name} type="name" - onChange={setName} + onChange={setVariantValue} />
-
+ + + + % + + + + Custom percentage + + +

Payload @@ -198,13 +240,11 @@ function AddVariant({ showDialog, closeDialog, save, validateName, editVariant, /> - {overrides.length > 0 ? ( + {overrides.length > 0 && (

Overrides

- ) : ( - undefined )} Variant name Weight + Weight Type diff --git a/frontend/src/component/feature/variant/variant-view-component.jsx b/frontend/src/component/feature/variant/variant-view-component.jsx index 8206261ab7..5ded86d604 100644 --- a/frontend/src/component/feature/variant/variant-view-component.jsx +++ b/frontend/src/component/feature/variant/variant-view-component.jsx @@ -1,11 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; - import { IconButton, Chip } from 'react-mdl'; import styles from './variant.scss'; import { UPDATE_FEATURE } from '../../../permissions'; +import { weightTypes } from './enums'; function VariantViewComponent({ variant, editVariant, removeVariant, hasPermission }) { + const { FIX } = weightTypes; return ( {variant.name} @@ -18,6 +19,7 @@ function VariantViewComponent({ variant, editVariant, removeVariant, hasPermissi )} {variant.weight / 10.0} % + {variant.weightType === FIX ? 'Fix' : 'Variable'} {hasPermission(UPDATE_FEATURE) ? ( diff --git a/frontend/src/component/feature/variant/variant.scss b/frontend/src/component/feature/variant/variant.scss index 9097083a92..829e5ba9df 100644 --- a/frontend/src/component/feature/variant/variant.scss +++ b/frontend/src/component/feature/variant/variant.scss @@ -2,11 +2,13 @@ width: 100%; max-width: 700px; - th, td { + th, + td { text-align: center; width: 100px; } - th:first-of-type, td:first-of-type { + th:first-of-type, + td:first-of-type { text-align: left; width: 100%; } @@ -16,10 +18,10 @@ } @media (max-width: 600px) { - th.labels { + th.labels { display: none; } - td.labels { + td.labels { display: none; } } @@ -58,4 +60,23 @@ td.actions { i { font-size: 18px; } -} \ No newline at end of file +} + +.inputWeight { + text-align: right; +} + +.flexCenter { + display: flex; + justify-content: center; + align-items: center; +} + +.flex { + display: flex; + align-items: center; +} + +.marginL10 { + margin-left: 10px; +}