mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Merge branch 'master' into fix/variant-list-popup
This commit is contained in:
		
						commit
						116a8e73f7
					
				| @ -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(); | ||||||
|  | |||||||
| @ -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> | ||||||
|                         } |                         } | ||||||
|  | |||||||
| @ -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{' '} | ||||||
|  | |||||||
| @ -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, | ||||||
|     }; |     }; | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user