From 388dfe45d3c87496157f7564a1a2562cf502e0af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivar=20Conradi=20=C3=98sthus?= Date: Tue, 16 Feb 2021 12:22:33 +0100 Subject: [PATCH] feat: allow custom context fields to define stickiness. (#241) --- .../context/form-context-component.jsx | 23 ++++++- .../flexible-rollout-strategy-container.jsx | 10 +++ .../strategy/flexible-rollout-strategy.jsx | 21 +++++-- .../strategy/strategy-configure-component.jsx | 2 +- .../update-variant-component-test.jsx.snap | 63 ++++++++++++++++--- .../update-variant-component-test.jsx | 3 + .../component/feature/variant/add-variant.jsx | 2 +- .../variant/update-variant-component.jsx | 48 ++++++++++++-- .../variant/update-variant-container.jsx | 7 +++ .../ui-config-store.test.js.snap | 6 +- frontend/src/store/ui-config/index.js | 2 +- 11 files changed, 161 insertions(+), 26 deletions(-) create mode 100644 frontend/src/component/feature/strategy/flexible-rollout-strategy-container.jsx diff --git a/frontend/src/component/context/form-context-component.jsx b/frontend/src/component/context/form-context-component.jsx index 43249c32c9..75b519d32e 100644 --- a/frontend/src/component/context/form-context-component.jsx +++ b/frontend/src/component/context/form-context-component.jsx @@ -1,6 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { Button, Chip, Textfield, Card, CardTitle, CardText, CardActions } from 'react-mdl'; +import { Button, Chip, Textfield, Card, CardTitle, CardText, CardActions, Checkbox } from 'react-mdl'; import { FormButtons, styles as commonStyles } from '../common'; import { trim } from '../common/util'; @@ -151,6 +151,27 @@ class AddContextComponent extends Component {
{contextField.legalValues.map(this.renderLegalValue)}
+
+
+
Custom stickiness (beta)
+

+ By enabling stickiness on this context field you can use it together with the + flexible-rollout strategy. This will guarantee a consistent behavior for specific values + of this context field. PS! Not all client SDK's support this feature yet!{' '} + + Read more + +

+ this.setValue('stickiness', !contextField.stickiness)} + /> +
diff --git a/frontend/src/component/feature/strategy/flexible-rollout-strategy-container.jsx b/frontend/src/component/feature/strategy/flexible-rollout-strategy-container.jsx new file mode 100644 index 0000000000..d79142db50 --- /dev/null +++ b/frontend/src/component/feature/strategy/flexible-rollout-strategy-container.jsx @@ -0,0 +1,10 @@ +import { connect } from 'react-redux'; +import FlexibleRolloutStrategy from './flexible-rollout-strategy'; + +const mapStateToProps = state => ({ + context: state.context.toJS(), +}); + +const FormAddContainer = connect(mapStateToProps, undefined)(FlexibleRolloutStrategy); + +export default FormAddContainer; diff --git a/frontend/src/component/feature/strategy/flexible-rollout-strategy.jsx b/frontend/src/component/feature/strategy/flexible-rollout-strategy.jsx index 58bddb6eb7..4aa5bd9d36 100644 --- a/frontend/src/component/feature/strategy/flexible-rollout-strategy.jsx +++ b/frontend/src/component/feature/strategy/flexible-rollout-strategy.jsx @@ -1,11 +1,12 @@ import React, { Component } from 'react'; import { Textfield } from 'react-mdl'; +import PropTypes from 'prop-types'; import strategyInputProps from './strategy-input-props'; import Select from '../../common/select'; import StrategyInputPercentage from './input-percentage'; -const stickinessOptions = [ +const builtInStickinessOptions = [ { key: 'default', label: 'default' }, { key: 'userId', label: 'userId' }, { key: 'sessionId', label: 'sessionId' }, @@ -13,16 +14,24 @@ const stickinessOptions = [ ]; export default class FlexibleRolloutStrategy extends Component { - static propTypes = strategyInputProps; + static propTypes = { ...strategyInputProps, context: PropTypes.array }; - onConfiguUpdate = (field, evt) => { + onUpdate = (field, evt) => { evt.preventDefault(); const value = evt.target.value; this.props.updateParameter(field, value); }; + resolveStickiness = () => { + const { context } = this.props; + return builtInStickinessOptions.concat( + context.filter(c => c.stickiness).map(c => ({ key: c.name, label: c.name })) + ); + }; + render() { const { editable, parameters, index } = this.props; + const stickinessOptions = this.resolveStickiness(); const rollout = parameters.rollout; const stickiness = parameters.stickiness; @@ -38,7 +47,7 @@ export default class FlexibleRolloutStrategy extends Component { minLabel="off" maxLabel="on" disabled={!editable} - onChange={evt => this.onConfiguUpdate('rollout', evt)} + onChange={evt => this.onUpdate('rollout', evt)} id={`${index}-groupId`} />
@@ -48,7 +57,7 @@ export default class FlexibleRolloutStrategy extends Component { options={stickinessOptions} value={stickiness} disabled={!editable} - onChange={evt => this.onConfiguUpdate('stickiness', evt)} + onChange={evt => this.onUpdate('stickiness', evt)} />   this.onConfiguUpdate('groupId', evt)} + onChange={evt => this.onUpdate('groupId', evt)} id={`${index}-groupId`} />{' '}
diff --git a/frontend/src/component/feature/strategy/strategy-configure-component.jsx b/frontend/src/component/feature/strategy/strategy-configure-component.jsx index fcb8eb11fd..742f8ac3f6 100644 --- a/frontend/src/component/feature/strategy/strategy-configure-component.jsx +++ b/frontend/src/component/feature/strategy/strategy-configure-component.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { Button, Card, CardTitle, CardText, CardMenu, IconButton, Icon } from 'react-mdl'; import { Link } from 'react-router-dom'; -import FlexibleRolloutStrategy from './flexible-rollout-strategy'; +import FlexibleRolloutStrategy from './flexible-rollout-strategy-container'; import DefaultStrategy from './default-strategy'; import GeneralStrategy from './general-strategy'; import UserWithIdStrategy from './user-with-id-strategy'; 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 af0fb988c9..8feacbe9a8 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 @@ -4,7 +4,6 @@ exports[`renders correctly with with variants 1`] = `

-

+

Add variant -

+
+
+ + +
+    + + By overriding the stickiness you can control which parameter you want to be used in order to ensure consistent traffic allocation across variants. + + + Read more + + +
+
`; @@ -170,7 +221,6 @@ exports[`renders correctly with without variants 1`] = `

-

+

Add variant -

+
`; @@ -209,7 +259,6 @@ exports[`renders correctly with without variants and no permissions 1`] = `
{ addVariant={jest.fn()} removeVariant={jest.fn()} updateVariant={jest.fn()} + stickinessOptions={['default']} hasPermission={permission => permission === UPDATE_FEATURE} /> @@ -34,6 +35,7 @@ test('renders correctly with without variants and no permissions', () => { addVariant={jest.fn()} removeVariant={jest.fn()} updateVariant={jest.fn()} + stickinessOptions={['default']} hasPermission={() => false} /> @@ -90,6 +92,7 @@ test('renders correctly with with variants', () => { addVariant={jest.fn()} removeVariant={jest.fn()} updateVariant={jest.fn()} + stickinessOptions={['default']} hasPermission={permission => permission === UPDATE_FEATURE} /> diff --git a/frontend/src/component/feature/variant/add-variant.jsx b/frontend/src/component/feature/variant/add-variant.jsx index fd0d4c8820..2bcc59a1ab 100644 --- a/frontend/src/component/feature/variant/add-variant.jsx +++ b/frontend/src/component/feature/variant/add-variant.jsx @@ -185,7 +185,7 @@ function AddVariant({ showDialog, closeDialog, save, validateName, editVariant, /> % - + Custom percentage diff --git a/frontend/src/component/feature/variant/update-variant-component.jsx b/frontend/src/component/feature/variant/update-variant-component.jsx index c3c2cc7d93..e2b8d220c4 100644 --- a/frontend/src/component/feature/variant/update-variant-component.jsx +++ b/frontend/src/component/feature/variant/update-variant-component.jsx @@ -5,8 +5,9 @@ import VariantViewComponent from './variant-view-component'; import styles from './variant.module.scss'; import { UPDATE_FEATURE } from '../../../permissions'; import AddVariant from './add-variant'; +import MySelect from '../../common/select'; -const initalState = { +const initialState = { showDialog: false, editVariant: undefined, editIndex: -1, @@ -15,11 +16,11 @@ const initalState = { class UpdateVariantComponent extends Component { constructor(props) { super(props); - this.state = { ...initalState }; + this.state = { ...initialState }; } closeDialog = () => { - this.setState({ ...initalState }); + this.setState({ ...initialState }); }; openAddVariant = e => { @@ -80,13 +81,45 @@ class UpdateVariantComponent extends Component { ); + renderStickiness = variants => { + const { updateStickiness, stickinessOptions } = this.props; + + if (!variants || variants.length < 2) { + return null; + } + + const value = variants[0].stickiness || 'default'; + const options = stickinessOptions.map(c => ({ key: c, label: c })); + + // guard on stickiness being disabled for context field. + if (!stickinessOptions.includes(value)) { + options.push({ key: value, label: value }); + } + + const onChange = event => updateStickiness(event.target.value); + + return ( +
+ +    + + By overriding the stickiness you can control which parameter you want to be used in order to ensure + consistent traffic allocation across variants.{' '} + + Read more + + +
+ ); + }; + render() { const { showDialog, editVariant, editIndex, title } = this.state; const { variants, addVariant, updateVariant } = this.props; const saveVariant = editVariant ? updateVariant.bind(null, editIndex) : addVariant; return ( -
+

Variants allows you to return a variant object if the feature toggle is considered enabled for the current request. When using variants you should use the{' '} @@ -96,11 +129,12 @@ class UpdateVariantComponent extends Component { {variants.length > 0 ? this.renderVariants(variants) :

No variants defined.

}
{this.props.hasPermission(UPDATE_FEATURE) ? ( -

+

Add variant -

+ {this.renderStickiness(variants)} +
) : null} ({ variants: ownProps.featureToggle.variants || [], + stickinessOptions: ['default', ...state.context.filter(c => c.stickiness).map(c => c.name)], hasPermission: ownProps.hasPermission, }); @@ -33,6 +34,12 @@ const mapDispatchToProps = (dispatch, ownProps) => ({ updateWeight(variants, 1000); requestUpdateFeatureToggleVariants(featureToggle, variants)(dispatch); }, + updateStickiness: stickiness => { + const { featureToggle } = ownProps; + const currentVariants = featureToggle.variants || []; + const variants = currentVariants.map(v => ({ ...v, stickiness })); + requestUpdateFeatureToggleVariants(featureToggle, variants)(dispatch); + }, }); export default connect(mapStateToProps, mapDispatchToProps)(UpdateFeatureToggleComponent); diff --git a/frontend/src/store/ui-config/__tests__/__snapshots__/ui-config-store.test.js.snap b/frontend/src/store/ui-config/__tests__/__snapshots__/ui-config-store.test.js.snap index 3cfbdc5136..ab7b753417 100644 --- a/frontend/src/store/ui-config/__tests__/__snapshots__/ui-config-store.test.js.snap +++ b/frontend/src/store/ui-config/__tests__/__snapshots__/ui-config-store.test.js.snap @@ -6,7 +6,7 @@ Object { "headerBackground": undefined, "links": Array [ Object { - "href": "https://unleash.github.io", + "href": "https://unleash.github.io?source=oss", "icon": "library_books", "title": "User documentation", "value": "User documentation", @@ -30,7 +30,7 @@ Object { "headerBackground": "red", "links": Array [ Object { - "href": "https://unleash.github.io", + "href": "https://unleash.github.io?source=oss", "icon": "library_books", "title": "User documentation", "value": "User documentation", @@ -54,7 +54,7 @@ Object { "headerBackground": "black", "links": Array [ Object { - "href": "https://unleash.github.io", + "href": "https://unleash.github.io?source=oss", "icon": "library_books", "title": "User documentation", "value": "User documentation", diff --git a/frontend/src/store/ui-config/index.js b/frontend/src/store/ui-config/index.js index 757747589e..22d4a28ae5 100644 --- a/frontend/src/store/ui-config/index.js +++ b/frontend/src/store/ui-config/index.js @@ -19,7 +19,7 @@ const DEFAULT = new $Map({ { value: 'User documentation', icon: 'library_books', - href: 'https://unleash.github.io', + href: 'https://unleash.github.io?source=oss', title: 'User documentation', }, {