1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-31 00:16:47 +01:00

feat: Use new Variants API (#518)

* feat: Use new Variants API

Co-authored-by: Fredrik Strand Oseberg <fredrik.no@gmail.com>
This commit is contained in:
Christopher Kolstad 2021-11-25 14:05:44 +01:00 committed by GitHub
parent b69606cd98
commit 83443627d9
4 changed files with 72 additions and 48 deletions

View File

@ -250,18 +250,18 @@ describe('feature toggle', () => {
cy.visit(`/projects/default/features2/${featureToggleName}/variants`); cy.visit(`/projects/default/features2/${featureToggleName}/variants`);
cy.intercept( cy.intercept(
'PATCH', 'PATCH',
`/api/admin/projects/default/features/${featureToggleName}`, `/api/admin/projects/default/features/${featureToggleName}/variants`,
req => { req => {
if (req.body.length === 1) { if (req.body.length === 1) {
expect(req.body[0].op).to.equal('add'); expect(req.body[0].op).to.equal('add');
expect(req.body[0].path).to.match(/variants/); expect(req.body[0].path).to.match(/\//);
expect(req.body[0].value.name).to.equal(variantName); expect(req.body[0].value.name).to.equal(variantName);
} else if (req.body.length === 2) { } else if (req.body.length === 2) {
expect(req.body[0].op).to.equal('replace'); expect(req.body[0].op).to.equal('replace');
expect(req.body[0].path).to.match(/weight/); expect(req.body[0].path).to.match(/weight/);
expect(req.body[0].value).to.equal(500); expect(req.body[0].value).to.equal(500);
expect(req.body[1].op).to.equal('add'); expect(req.body[1].op).to.equal('add');
expect(req.body[1].path).to.match(/variants/); expect(req.body[1].path).to.match(/\//);
expect(req.body[1].value.name).to.equal(secondVariantName); expect(req.body[1].value.name).to.equal(secondVariantName);
} }
} }
@ -291,7 +291,7 @@ describe('feature toggle', () => {
cy.get('[data-test=VARIANT_WEIGHT_INPUT]').clear().type('15'); cy.get('[data-test=VARIANT_WEIGHT_INPUT]').clear().type('15');
cy.intercept( cy.intercept(
'PATCH', 'PATCH',
`/api/admin/projects/default/features/${featureToggleName}`, `/api/admin/projects/default/features/${featureToggleName}/variants`,
req => { req => {
expect(req.body[0].op).to.equal('replace'); expect(req.body[0].op).to.equal('replace');
expect(req.body[0].path).to.match(/weight/); expect(req.body[0].path).to.match(/weight/);
@ -320,10 +320,10 @@ describe('feature toggle', () => {
cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click(); cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click();
cy.intercept( cy.intercept(
'PATCH', 'PATCH',
`/api/admin/projects/default/features/${featureToggleName}`, `/api/admin/projects/default/features/${featureToggleName}/variants`,
req => { req => {
const e = req.body.find(e => e.op === 'remove'); const e = req.body.find(e => e.op === 'remove');
expect(e.path).to.match(/variants/); expect(e.path).to.match(/\//);
} }
).as('delete'); ).as('delete');
cy.get(`[data-test=VARIANT_DELETE_BUTTON_${variantName}]`).click(); cy.get(`[data-test=VARIANT_DELETE_BUTTON_${variantName}]`).click();

View File

@ -41,6 +41,7 @@ const AddVariant = ({
save, save,
editVariant, editVariant,
validateName, validateName,
validateWeight,
title, title,
editing, editing,
}) => { }) => {
@ -115,9 +116,14 @@ const AddVariant = ({
setError({}); setError({});
e.preventDefault(); e.preventDefault();
const validationError = validateName(data.name); const nameValidation = validateName(data.name);
if (validationError) { if (nameValidation) {
setError(validationError); setError(nameValidation);
return;
}
const weightValidation = validateWeight(data.weight);
if (weightValidation) {
setError(weightValidation);
return; return;
} }
@ -240,8 +246,9 @@ const AddVariant = ({
/> />
<br /> <br />
<Grid container> <Grid container>
{/* If we're editing, we need to have at least 2 existing variants, since we require at least 1 variable. If adding, we could be adding nr 2, and as such should be allowed to set weightType to variable */}
<ConditionallyRender <ConditionallyRender
condition={variants.length > 0} condition={(editing && variants.length > 1) || (!editing && variants.length > 0)}
show={ show={
<Grid <Grid
item item
@ -296,6 +303,8 @@ const AddVariant = ({
onChange={e => { onChange={e => {
setVariantValue(e); setVariantValue(e);
}} }}
aria-valuemin={0}
aria-valuemax={100}
/> />
</Grid> </Grid>
} }

View File

@ -2,14 +2,7 @@ import classnames from 'classnames';
import * as jsonpatch from 'fast-json-patch'; import * as jsonpatch from 'fast-json-patch';
import styles from './variants.module.scss'; import styles from './variants.module.scss';
import { import { Table, TableBody, TableCell, TableHead, TableRow, Typography } from '@material-ui/core';
Table,
TableBody,
TableCell,
TableHead,
TableRow,
Typography,
} from '@material-ui/core';
import AddVariant from './AddFeatureVariant/AddFeatureVariant'; import AddVariant from './AddFeatureVariant/AddFeatureVariant';
import { useContext, useEffect, useState } from 'react'; import { useContext, useEffect, useState } from 'react';
@ -29,16 +22,17 @@ import { updateWeight } from '../../../../common/util';
import cloneDeep from 'lodash.clonedeep'; import cloneDeep from 'lodash.clonedeep';
import useDeleteVariantMarkup from './FeatureVariantsListItem/useDeleteVariantMarkup'; import useDeleteVariantMarkup from './FeatureVariantsListItem/useDeleteVariantMarkup';
import PermissionButton from '../../../../common/PermissionButton/PermissionButton'; import PermissionButton from '../../../../common/PermissionButton/PermissionButton';
import { mutate } from 'swr';
const FeatureOverviewVariants = () => { const FeatureOverviewVariants = () => {
const { hasAccess } = useContext(AccessContext); const { hasAccess } = useContext(AccessContext);
const { projectId, featureId } = useParams<IFeatureViewParams>(); const { projectId, featureId } = useParams<IFeatureViewParams>();
const { feature, refetch } = useFeature(projectId, featureId); const { feature, FEATURE_CACHE_KEY } = useFeature(projectId, featureId);
const [variants, setVariants] = useState<IFeatureVariant[]>([]); const [variants, setVariants] = useState<IFeatureVariant[]>([]);
const [editing, setEditing] = useState(false); const [editing, setEditing] = useState(false);
const { context } = useUnleashContext(); const { context } = useUnleashContext();
const { toast, setToastData } = useToast(); const { toast, setToastData } = useToast();
const { patchFeatureToggle } = useFeatureApi(); const { patchFeatureVariants } = useFeatureApi();
const [editVariant, setEditVariant] = useState({}); const [editVariant, setEditVariant] = useState({});
const [showAddVariant, setShowAddVariant] = useState(false); const [showAddVariant, setShowAddVariant] = useState(false);
const [stickinessOptions, setStickinessOptions] = useState([]); const [stickinessOptions, setStickinessOptions] = useState([]);
@ -110,7 +104,7 @@ const FeatureOverviewVariants = () => {
return ( return (
<section style={{ paddingTop: '16px' }}> <section style={{ paddingTop: '16px' }}>
<GeneralSelect <GeneralSelect
label="Stickiness" label='Stickiness'
options={options} options={options}
value={value} value={value}
onChange={onChange} onChange={onChange}
@ -124,9 +118,9 @@ const FeatureOverviewVariants = () => {
is used to ensure consistent traffic allocation across is used to ensure consistent traffic allocation across
variants.{' '} variants.{' '}
<a <a
href="https://docs.getunleash.io/advanced/toggle_variants" href='https://docs.getunleash.io/advanced/toggle_variants'
target="_blank" target='_blank'
rel="noreferrer" rel='noreferrer'
> >
Read more Read more
</a> </a>
@ -145,8 +139,10 @@ const FeatureOverviewVariants = () => {
if (patch.length === 0) return; if (patch.length === 0) return;
try { try {
await patchFeatureToggle(projectId, featureId, patch); const res = await patchFeatureVariants(projectId, featureId, patch);
refetch(); // @ts-ignore
const { variants } = await res.json();
mutate(FEATURE_CACHE_KEY, { ...feature, variants }, false);
setToastData({ setToastData({
show: true, show: true,
type: 'success', type: 'success',
@ -166,7 +162,7 @@ const FeatureOverviewVariants = () => {
try { try {
await updateVariants( await updateVariants(
updatedVariants, updatedVariants,
'Successfully removed variant' 'Successfully removed variant',
); );
} catch (e) { } catch (e) {
setToastData({ setToastData({
@ -179,7 +175,7 @@ const FeatureOverviewVariants = () => {
const updateVariant = async (variant: IFeatureVariant) => { const updateVariant = async (variant: IFeatureVariant) => {
const updatedVariants = cloneDeep(variants); const updatedVariants = cloneDeep(variants);
const variantIdxToUpdate = updatedVariants.findIndex( const variantIdxToUpdate = updatedVariants.findIndex(
(v: IFeatureVariant) => v.name === variant.name (v: IFeatureVariant) => v.name === variant.name,
); );
updatedVariants[variantIdxToUpdate] = variant; updatedVariants[variantIdxToUpdate] = variant;
await updateVariants(updatedVariants, 'Successfully updated variant'); await updateVariants(updatedVariants, 'Successfully updated variant');
@ -194,30 +190,36 @@ const FeatureOverviewVariants = () => {
await updateVariants( await updateVariants(
[...variants, variant], [...variants, variant],
'Successfully added a variant' 'Successfully added a variant',
); );
}; };
const updateVariants = async ( const updateVariants = async (
variants: IFeatureVariant[], variants: IFeatureVariant[],
successText: string successText: string,
) => { ) => {
const newVariants = updateWeight(variants, 1000); const newVariants = updateWeight(variants, 1000);
const patch = createPatch(newVariants); const patch = createPatch(newVariants);
if (patch.length === 0) return; if (patch.length === 0) return;
await patchFeatureToggle(projectId, featureId, patch) try {
.then(() => { const res = await patchFeatureVariants(projectId, featureId, patch);
refetch(); // @ts-ignore
setToastData({ const { variants } = await res.json();
show: true, mutate(FEATURE_CACHE_KEY, { ...feature, variants }, false);
type: 'success', setToastData({
text: successText, show: true,
}); type: 'success',
}) text: successText,
.catch(e => {
throw e;
}); });
} catch (e) {
setToastData({
show: true,
type: 'error',
text: e.toString(),
});
}
}; };
const validateName = (name: string) => { const validateName = (name: string) => {
@ -248,17 +250,13 @@ const FeatureOverviewVariants = () => {
}); });
const createPatch = (newVariants: IFeatureVariant[]) => { const createPatch = (newVariants: IFeatureVariant[]) => {
const patch = jsonpatch return jsonpatch
.compare(feature.variants, newVariants) .compare(feature.variants, newVariants);
.map(patch => {
return { ...patch, path: `/variants${patch.path}` };
});
return patch;
}; };
return ( return (
<section style={{ padding: '16px' }}> <section style={{ padding: '16px' }}>
<Typography variant="body1"> <Typography variant='body1'>
Variants allows you to return a variant object if the feature Variants allows you to return a variant object if the feature
toggle is considered enabled for the current request. When using toggle is considered enabled for the current request. When using
variants you should use the{' '} variants you should use the{' '}

View File

@ -1,6 +1,7 @@
import { IFeatureToggleDTO } from '../../../../interfaces/featureToggle'; import { IFeatureToggleDTO } from '../../../../interfaces/featureToggle';
import { ITag } from '../../../../interfaces/tags'; import { ITag } from '../../../../interfaces/tags';
import useAPI from '../useApi/useApi'; import useAPI from '../useApi/useApi';
import { Operation } from 'fast-json-patch';
const useFeatureApi = () => { const useFeatureApi = () => {
const { makeRequest, createRequest, errors, loading } = useAPI({ const { makeRequest, createRequest, errors, loading } = useAPI({
@ -182,6 +183,21 @@ const useFeatureApi = () => {
} }
}; };
const patchFeatureVariants = async (projectId: string, featureId: string, patchPayload: Operation[]) => {
const path = `api/admin/projects/${projectId}/features/${featureId}/variants`;
const req = createRequest(path, {
method: 'PATCH',
body: JSON.stringify(patchPayload),
});
try {
const res = await makeRequest(req.caller, req.id);
return res;
} catch (e) {
throw e;
}
};
const cloneFeatureToggle = async ( const cloneFeatureToggle = async (
projectId: string, projectId: string,
featureId: string, featureId: string,
@ -213,6 +229,7 @@ const useFeatureApi = () => {
deleteTagFromFeature, deleteTagFromFeature,
archiveFeatureToggle, archiveFeatureToggle,
patchFeatureToggle, patchFeatureToggle,
patchFeatureVariants,
cloneFeatureToggle, cloneFeatureToggle,
loading, loading,
}; };