diff --git a/frontend/cypress/integration/feature-toggle/feature.spec.js b/frontend/cypress/integration/feature-toggle/feature.spec.js
index e82a0bf968..3fc213e00a 100644
--- a/frontend/cypress/integration/feature-toggle/feature.spec.js
+++ b/frontend/cypress/integration/feature-toggle/feature.spec.js
@@ -250,18 +250,18 @@ describe('feature toggle', () => {
cy.visit(`/projects/default/features2/${featureToggleName}/variants`);
cy.intercept(
'PATCH',
- `/api/admin/projects/default/features/${featureToggleName}`,
+ `/api/admin/projects/default/features/${featureToggleName}/variants`,
req => {
if (req.body.length === 1) {
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);
} else if (req.body.length === 2) {
expect(req.body[0].op).to.equal('replace');
expect(req.body[0].path).to.match(/weight/);
expect(req.body[0].value).to.equal(500);
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);
}
}
@@ -291,7 +291,7 @@ describe('feature toggle', () => {
cy.get('[data-test=VARIANT_WEIGHT_INPUT]').clear().type('15');
cy.intercept(
'PATCH',
- `/api/admin/projects/default/features/${featureToggleName}`,
+ `/api/admin/projects/default/features/${featureToggleName}/variants`,
req => {
expect(req.body[0].op).to.equal('replace');
expect(req.body[0].path).to.match(/weight/);
@@ -320,10 +320,10 @@ describe('feature toggle', () => {
cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click();
cy.intercept(
'PATCH',
- `/api/admin/projects/default/features/${featureToggleName}`,
+ `/api/admin/projects/default/features/${featureToggleName}/variants`,
req => {
const e = req.body.find(e => e.op === 'remove');
- expect(e.path).to.match(/variants/);
+ expect(e.path).to.match(/\//);
}
).as('delete');
cy.get(`[data-test=VARIANT_DELETE_BUTTON_${variantName}]`).click();
diff --git a/frontend/src/component/feature/FeatureView2/FeatureVariants/FeatureVariantsList/AddFeatureVariant/AddFeatureVariant.tsx b/frontend/src/component/feature/FeatureView2/FeatureVariants/FeatureVariantsList/AddFeatureVariant/AddFeatureVariant.tsx
index 4d9a3b7153..43cd726101 100644
--- a/frontend/src/component/feature/FeatureView2/FeatureVariants/FeatureVariantsList/AddFeatureVariant/AddFeatureVariant.tsx
+++ b/frontend/src/component/feature/FeatureView2/FeatureVariants/FeatureVariantsList/AddFeatureVariant/AddFeatureVariant.tsx
@@ -41,6 +41,7 @@ const AddVariant = ({
save,
editVariant,
validateName,
+ validateWeight,
title,
editing,
}) => {
@@ -115,9 +116,14 @@ const AddVariant = ({
setError({});
e.preventDefault();
- const validationError = validateName(data.name);
- if (validationError) {
- setError(validationError);
+ const nameValidation = validateName(data.name);
+ if (nameValidation) {
+ setError(nameValidation);
+ return;
+ }
+ const weightValidation = validateWeight(data.weight);
+ if (weightValidation) {
+ setError(weightValidation);
return;
}
@@ -240,8 +246,9 @@ const AddVariant = ({
/>
+ {/* 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 */}
0}
+ condition={(editing && variants.length > 1) || (!editing && variants.length > 0)}
show={
{
setVariantValue(e);
}}
+ aria-valuemin={0}
+ aria-valuemax={100}
/>
}
diff --git a/frontend/src/component/feature/FeatureView2/FeatureVariants/FeatureVariantsList/FeatureVariantsList.tsx b/frontend/src/component/feature/FeatureView2/FeatureVariants/FeatureVariantsList/FeatureVariantsList.tsx
index ec8ea38dea..b468a19f23 100644
--- a/frontend/src/component/feature/FeatureView2/FeatureVariants/FeatureVariantsList/FeatureVariantsList.tsx
+++ b/frontend/src/component/feature/FeatureView2/FeatureVariants/FeatureVariantsList/FeatureVariantsList.tsx
@@ -2,14 +2,7 @@ import classnames from 'classnames';
import * as jsonpatch from 'fast-json-patch';
import styles from './variants.module.scss';
-import {
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableRow,
- Typography,
-} from '@material-ui/core';
+import { Table, TableBody, TableCell, TableHead, TableRow, Typography } from '@material-ui/core';
import AddVariant from './AddFeatureVariant/AddFeatureVariant';
import { useContext, useEffect, useState } from 'react';
@@ -29,16 +22,17 @@ import { updateWeight } from '../../../../common/util';
import cloneDeep from 'lodash.clonedeep';
import useDeleteVariantMarkup from './FeatureVariantsListItem/useDeleteVariantMarkup';
import PermissionButton from '../../../../common/PermissionButton/PermissionButton';
+import { mutate } from 'swr';
const FeatureOverviewVariants = () => {
const { hasAccess } = useContext(AccessContext);
const { projectId, featureId } = useParams();
- const { feature, refetch } = useFeature(projectId, featureId);
+ const { feature, FEATURE_CACHE_KEY } = useFeature(projectId, featureId);
const [variants, setVariants] = useState([]);
const [editing, setEditing] = useState(false);
const { context } = useUnleashContext();
const { toast, setToastData } = useToast();
- const { patchFeatureToggle } = useFeatureApi();
+ const { patchFeatureVariants } = useFeatureApi();
const [editVariant, setEditVariant] = useState({});
const [showAddVariant, setShowAddVariant] = useState(false);
const [stickinessOptions, setStickinessOptions] = useState([]);
@@ -110,7 +104,7 @@ const FeatureOverviewVariants = () => {
return (
{
is used to ensure consistent traffic allocation across
variants.{' '}
Read more
@@ -145,8 +139,10 @@ const FeatureOverviewVariants = () => {
if (patch.length === 0) return;
try {
- await patchFeatureToggle(projectId, featureId, patch);
- refetch();
+ const res = await patchFeatureVariants(projectId, featureId, patch);
+ // @ts-ignore
+ const { variants } = await res.json();
+ mutate(FEATURE_CACHE_KEY, { ...feature, variants }, false);
setToastData({
show: true,
type: 'success',
@@ -166,7 +162,7 @@ const FeatureOverviewVariants = () => {
try {
await updateVariants(
updatedVariants,
- 'Successfully removed variant'
+ 'Successfully removed variant',
);
} catch (e) {
setToastData({
@@ -179,7 +175,7 @@ const FeatureOverviewVariants = () => {
const updateVariant = async (variant: IFeatureVariant) => {
const updatedVariants = cloneDeep(variants);
const variantIdxToUpdate = updatedVariants.findIndex(
- (v: IFeatureVariant) => v.name === variant.name
+ (v: IFeatureVariant) => v.name === variant.name,
);
updatedVariants[variantIdxToUpdate] = variant;
await updateVariants(updatedVariants, 'Successfully updated variant');
@@ -194,30 +190,36 @@ const FeatureOverviewVariants = () => {
await updateVariants(
[...variants, variant],
- 'Successfully added a variant'
+ 'Successfully added a variant',
);
};
const updateVariants = async (
variants: IFeatureVariant[],
- successText: string
+ successText: string,
) => {
const newVariants = updateWeight(variants, 1000);
const patch = createPatch(newVariants);
if (patch.length === 0) return;
- await patchFeatureToggle(projectId, featureId, patch)
- .then(() => {
- refetch();
- setToastData({
- show: true,
- type: 'success',
- text: successText,
- });
- })
- .catch(e => {
- throw e;
+ try {
+ const res = await patchFeatureVariants(projectId, featureId, patch);
+ // @ts-ignore
+ const { variants } = await res.json();
+ mutate(FEATURE_CACHE_KEY, { ...feature, variants }, false);
+ setToastData({
+ show: true,
+ type: 'success',
+ text: successText,
});
+ } catch (e) {
+ setToastData({
+ show: true,
+ type: 'error',
+ text: e.toString(),
+ });
+ }
+
};
const validateName = (name: string) => {
@@ -248,17 +250,13 @@ const FeatureOverviewVariants = () => {
});
const createPatch = (newVariants: IFeatureVariant[]) => {
- const patch = jsonpatch
- .compare(feature.variants, newVariants)
- .map(patch => {
- return { ...patch, path: `/variants${patch.path}` };
- });
- return patch;
+ return jsonpatch
+ .compare(feature.variants, newVariants);
};
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{' '}
diff --git a/frontend/src/hooks/api/actions/useFeatureApi/useFeatureApi.ts b/frontend/src/hooks/api/actions/useFeatureApi/useFeatureApi.ts
index 0921a49599..360f5c0187 100644
--- a/frontend/src/hooks/api/actions/useFeatureApi/useFeatureApi.ts
+++ b/frontend/src/hooks/api/actions/useFeatureApi/useFeatureApi.ts
@@ -1,6 +1,7 @@
import { IFeatureToggleDTO } from '../../../../interfaces/featureToggle';
import { ITag } from '../../../../interfaces/tags';
import useAPI from '../useApi/useApi';
+import { Operation } from 'fast-json-patch';
const useFeatureApi = () => {
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 (
projectId: string,
featureId: string,
@@ -213,6 +229,7 @@ const useFeatureApi = () => {
deleteTagFromFeature,
archiveFeatureToggle,
patchFeatureToggle,
+ patchFeatureVariants,
cloneFeatureToggle,
loading,
};