mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +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:
		
							parent
							
								
									b69606cd98
								
							
						
					
					
						commit
						83443627d9
					
				| @ -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(); | ||||
|  | ||||
| @ -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 = ({ | ||||
|                 /> | ||||
|                 <br /> | ||||
|                 <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 | ||||
|                         condition={variants.length > 0} | ||||
|                         condition={(editing && variants.length > 1) || (!editing && variants.length > 0)} | ||||
|                         show={ | ||||
|                             <Grid | ||||
|                                 item | ||||
| @ -296,6 +303,8 @@ const AddVariant = ({ | ||||
|                                     onChange={e => { | ||||
|                                         setVariantValue(e); | ||||
|                                     }} | ||||
|                                     aria-valuemin={0} | ||||
|                                     aria-valuemax={100} | ||||
|                                 /> | ||||
|                             </Grid> | ||||
|                         } | ||||
|  | ||||
| @ -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<IFeatureViewParams>(); | ||||
|     const { feature, refetch } = useFeature(projectId, featureId); | ||||
|     const { feature, FEATURE_CACHE_KEY } = useFeature(projectId, featureId); | ||||
|     const [variants, setVariants] = useState<IFeatureVariant[]>([]); | ||||
|     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 ( | ||||
|             <section style={{ paddingTop: '16px' }}> | ||||
|                 <GeneralSelect | ||||
|                     label="Stickiness" | ||||
|                     label='Stickiness' | ||||
|                     options={options} | ||||
|                     value={value} | ||||
|                     onChange={onChange} | ||||
| @ -124,9 +118,9 @@ const FeatureOverviewVariants = () => { | ||||
|                     is used to ensure consistent traffic allocation across | ||||
|                     variants.{' '} | ||||
|                     <a | ||||
|                         href="https://docs.getunleash.io/advanced/toggle_variants" | ||||
|                         target="_blank" | ||||
|                         rel="noreferrer" | ||||
|                         href='https://docs.getunleash.io/advanced/toggle_variants' | ||||
|                         target='_blank' | ||||
|                         rel='noreferrer' | ||||
|                     > | ||||
|                         Read more | ||||
|                     </a> | ||||
| @ -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 ( | ||||
|         <section style={{ padding: '16px' }}> | ||||
|             <Typography variant="body1"> | ||||
|             <Typography variant='body1'> | ||||
|                 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{' '} | ||||
|  | ||||
| @ -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, | ||||
|     }; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user