mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	refactor: remove variants per environment feature flag (#3102)
https://linear.app/unleash/issue/2-428/clean-up-feature-flag-once-were-done-with-the-migration Cleans up the variants per environment feature flag due to GA.
This commit is contained in:
		
							parent
							
								
									0a32647d75
								
							
						
					
					
						commit
						8729f082d2
					
				| @ -232,36 +232,29 @@ describe('feature', () => { | ||||
|         cy.wait('@addStrategyToFeature'); | ||||
|     }); | ||||
| 
 | ||||
|     it('can add two variant to the feature', () => { | ||||
|     it('can add two variants to the development environment', () => { | ||||
|         cy.visit(`/projects/default/features/${featureToggleName}/variants`); | ||||
| 
 | ||||
|         cy.intercept( | ||||
|             'PATCH', | ||||
|             `/api/admin/projects/default/features/${featureToggleName}/variants`, | ||||
|             `/api/admin/projects/default/features/${featureToggleName}/environments/development/variants`, | ||||
|             req => { | ||||
|                 if (req.body.length === 1) { | ||||
|                     expect(req.body[0].op).to.equal('add'); | ||||
|                     expect(req.body[0].path).to.match(/\//); | ||||
|                     expect(req.body[0].value.name).to.equal(variant1); | ||||
|                 } 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(/\//); | ||||
|                     expect(req.body[1].value.name).to.equal(variant2); | ||||
|                 } | ||||
|                 expect(req.body[0].op).to.equal('add'); | ||||
|                 expect(req.body[0].path).to.equal('/0'); | ||||
|                 expect(req.body[0].value.name).to.equal(variant1); | ||||
|                 expect(req.body[0].value.weight).to.equal(500); | ||||
|                 expect(req.body[1].op).to.equal('add'); | ||||
|                 expect(req.body[1].path).to.equal('/1'); | ||||
|                 expect(req.body[1].value.name).to.equal(variant2); | ||||
|                 expect(req.body[1].value.weight).to.equal(500); | ||||
|             } | ||||
|         ).as('variantCreation'); | ||||
| 
 | ||||
|         cy.get('[data-testid=ADD_VARIANT_BUTTON]').click(); | ||||
|         cy.get('[data-testid=ADD_VARIANT_BUTTON]').first().click(); | ||||
|         cy.wait(1000); | ||||
|         cy.get('[data-testid=VARIANT_NAME_INPUT]').type(variant1); | ||||
|         cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click(); | ||||
|         cy.wait('@variantCreation'); | ||||
|         cy.get('[data-testid=ADD_VARIANT_BUTTON]').click(); | ||||
|         cy.wait(1000); | ||||
|         cy.get('[data-testid=VARIANT_NAME_INPUT]').type(variant2); | ||||
|         cy.get('[data-testid=MODAL_ADD_VARIANT_BUTTON]').click(); | ||||
|         cy.get('[data-testid=VARIANT_NAME_INPUT]').last().type(variant2); | ||||
|         cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click(); | ||||
|         cy.wait('@variantCreation'); | ||||
|     }); | ||||
| @ -269,60 +262,61 @@ describe('feature', () => { | ||||
|     it('can set weight to fixed value for one of the variants', () => { | ||||
|         cy.visit(`/projects/default/features/${featureToggleName}/variants`); | ||||
| 
 | ||||
|         cy.get(`[data-testid=VARIANT_EDIT_BUTTON_${variant1}]`).click(); | ||||
|         cy.get('[data-testid=EDIT_VARIANTS_BUTTON]').click(); | ||||
|         cy.wait(1000); | ||||
|         cy.get('[data-testid=VARIANT_NAME_INPUT]') | ||||
|             .last() | ||||
|             .children() | ||||
|             .find('input') | ||||
|             .should('have.attr', 'disabled'); | ||||
|         cy.get('[data-testid=VARIANT_WEIGHT_CHECK]').find('input').check(); | ||||
|         cy.get('[data-testid=VARIANT_WEIGHT_INPUT]').clear().type('15'); | ||||
|         cy.get('[data-testid=VARIANT_WEIGHT_CHECK]') | ||||
|             .last() | ||||
|             .find('input') | ||||
|             .check(); | ||||
|         cy.get('[data-testid=VARIANT_WEIGHT_INPUT]').last().clear().type('15'); | ||||
| 
 | ||||
|         cy.intercept( | ||||
|             'PATCH', | ||||
|             `/api/admin/projects/default/features/${featureToggleName}/variants`, | ||||
|             `/api/admin/projects/default/features/${featureToggleName}/environments/development/variants`, | ||||
|             req => { | ||||
|                 expect(req.body[0].op).to.equal('replace'); | ||||
|                 expect(req.body[0].path).to.match(/weight/); | ||||
|                 expect(req.body[0].value).to.equal(850); | ||||
|                 expect(req.body[0].path).to.equal('/1/weightType'); | ||||
|                 expect(req.body[0].value).to.equal('fix'); | ||||
|                 expect(req.body[1].op).to.equal('replace'); | ||||
|                 expect(req.body[1].path).to.match(/weightType/); | ||||
|                 expect(req.body[1].value).to.equal('fix'); | ||||
|                 expect(req.body[1].path).to.equal('/1/weight'); | ||||
|                 expect(req.body[1].value).to.equal(150); | ||||
|                 expect(req.body[2].op).to.equal('replace'); | ||||
|                 expect(req.body[2].path).to.match(/weight/); | ||||
|                 expect(req.body[2].value).to.equal(150); | ||||
|                 expect(req.body[2].path).to.equal('/0/weight'); | ||||
|                 expect(req.body[2].value).to.equal(850); | ||||
|             } | ||||
|         ).as('variantUpdate'); | ||||
| 
 | ||||
|         cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click(); | ||||
|         cy.wait('@variantUpdate'); | ||||
|         cy.get(`[data-testid=VARIANT_WEIGHT_${variant1}]`).should( | ||||
|         cy.get(`[data-testid=VARIANT_WEIGHT_${variant2}]`).should( | ||||
|             'have.text', | ||||
|             '15 %' | ||||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it('can delete variant', () => { | ||||
|         const variantName = 'to-be-deleted'; | ||||
| 
 | ||||
|         cy.visit(`/projects/default/features/${featureToggleName}/variants`); | ||||
|         cy.get('[data-testid=ADD_VARIANT_BUTTON]').click(); | ||||
|         cy.get('[data-testid=EDIT_VARIANTS_BUTTON]').click(); | ||||
|         cy.wait(1000); | ||||
|         cy.get('[data-testid=VARIANT_NAME_INPUT]').type(variantName); | ||||
|         cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click(); | ||||
|         cy.get(`[data-testid=VARIANT_DELETE_BUTTON_${variant2}]`).click(); | ||||
| 
 | ||||
|         cy.intercept( | ||||
|             'PATCH', | ||||
|             `/api/admin/projects/default/features/${featureToggleName}/variants`, | ||||
|             `/api/admin/projects/default/features/${featureToggleName}/environments/development/variants`, | ||||
|             req => { | ||||
|                 const patch = req.body.find( | ||||
|                     (patch: Record<string, string>) => patch.op === 'remove' | ||||
|                 ); | ||||
|                 expect(patch.path).to.match(/\//); | ||||
|                 expect(req.body[0].op).to.equal('remove'); | ||||
|                 expect(req.body[0].path).to.equal('/1'); | ||||
|                 expect(req.body[1].op).to.equal('replace'); | ||||
|                 expect(req.body[1].path).to.equal('/0/weight'); | ||||
|                 expect(req.body[1].value).to.equal(1000); | ||||
|             } | ||||
|         ).as('delete'); | ||||
| 
 | ||||
|         cy.get(`[data-testid=VARIANT_DELETE_BUTTON_${variantName}]`).click(); | ||||
|         cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click(); | ||||
|         cy.wait('@delete'); | ||||
|     }); | ||||
|  | ||||
| @ -119,7 +119,11 @@ describe('imports', () => { | ||||
|         // cy.contains('Import completed');
 | ||||
| 
 | ||||
|         cy.visit(`/projects/default/features/${randomFeatureName}`); | ||||
|         cy.contains('enabled in development'); | ||||
|         cy.get( | ||||
|             "[data-testid='feature-toggle-status'] input[type='checkbox']:checked" | ||||
|         ) | ||||
|             .closest('div') | ||||
|             .contains('development'); | ||||
|         cy.contains('50%'); | ||||
|     }); | ||||
| }); | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| import FeatureOverviewMetaData from './FeatureOverviewMetaData/FeatureOverviewMetaData'; | ||||
| import FeatureOverviewEnvironments from './FeatureOverviewEnvironments/FeatureOverviewEnvironments'; | ||||
| import FeatureOverviewEnvSwitches from './FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitches'; | ||||
| import { Routes, Route, useNavigate } from 'react-router-dom'; | ||||
| import { FeatureStrategyCreate } from 'component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate'; | ||||
| import { SidebarModal } from 'component/common/SidebarModal/SidebarModal'; | ||||
| @ -10,8 +9,6 @@ import { | ||||
| } from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit'; | ||||
| import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; | ||||
| import { usePageTitle } from 'hooks/usePageTitle'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import { FeatureOverviewSidePanel } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel'; | ||||
| import { useHiddenEnvironments } from 'hooks/useHiddenEnvironments'; | ||||
| import { styled } from '@mui/material'; | ||||
| @ -34,7 +31,6 @@ const StyledMainContent = styled('div')(({ theme }) => ({ | ||||
| })); | ||||
| 
 | ||||
| const FeatureOverview = () => { | ||||
|     const { uiConfig } = useUiConfig(); | ||||
|     const navigate = useNavigate(); | ||||
|     const projectId = useRequiredPathParam('projectId'); | ||||
|     const featureId = useRequiredPathParam('featureId'); | ||||
| @ -48,20 +44,9 @@ const FeatureOverview = () => { | ||||
|         <StyledContainer> | ||||
|             <div> | ||||
|                 <FeatureOverviewMetaData /> | ||||
|                 <ConditionallyRender | ||||
|                     condition={Boolean(uiConfig.flags.variantsPerEnvironment)} | ||||
|                     show={ | ||||
|                         <FeatureOverviewSidePanel | ||||
|                             hiddenEnvironments={hiddenEnvironments} | ||||
|                             setHiddenEnvironments={setHiddenEnvironments} | ||||
|                         /> | ||||
|                     } | ||||
|                     elseShow={ | ||||
|                         <FeatureOverviewEnvSwitches | ||||
|                             hiddenEnvironments={hiddenEnvironments} | ||||
|                             setHiddenEnvironments={setHiddenEnvironments} | ||||
|                         /> | ||||
|                     } | ||||
|                 <FeatureOverviewSidePanel | ||||
|                     hiddenEnvironments={hiddenEnvironments} | ||||
|                     setHiddenEnvironments={setHiddenEnvironments} | ||||
|                 /> | ||||
|             </div> | ||||
|             <StyledMainContent> | ||||
|  | ||||
| @ -145,17 +145,6 @@ const FeatureOverviewMetaData = () => { | ||||
|                     /> | ||||
|                 </StyledBody> | ||||
|             </StyledPaddingContainerTop> | ||||
|             <ConditionallyRender | ||||
|                 condition={ | ||||
|                     tags.length > 0 && | ||||
|                     !Boolean(uiConfig.flags.variantsPerEnvironment) | ||||
|                 } | ||||
|                 show={ | ||||
|                     <StyledPaddingContainerBottom> | ||||
|                         <FeatureOverviewTags projectId={projectId} /> | ||||
|                     </StyledPaddingContainerBottom> | ||||
|                 } | ||||
|             /> | ||||
|         </StyledContainer> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -59,7 +59,7 @@ export const FeatureOverviewSidePanelEnvironmentSwitches = ({ | ||||
|         environment => environment.enabled && environment.variants?.length | ||||
|     ); | ||||
|     return ( | ||||
|         <StyledContainer> | ||||
|         <StyledContainer data-testid="feature-toggle-status"> | ||||
|             {header} | ||||
|             {feature.environments.map(environment => { | ||||
|                 const strategiesLabel = | ||||
|  | ||||
| @ -284,6 +284,7 @@ export const EnvironmentVariantsModal = ({ | ||||
|                         </StyledName> | ||||
|                     </div> | ||||
|                     <PermissionButton | ||||
|                         data-testid="MODAL_ADD_VARIANT_BUTTON" | ||||
|                         onClick={() => | ||||
|                             setVariantsEdit(variantsEdit => [ | ||||
|                                 ...variantsEdit, | ||||
| @ -400,6 +401,7 @@ export const EnvironmentVariantsModal = ({ | ||||
| 
 | ||||
|                     <StyledButtonContainer> | ||||
|                         <Button | ||||
|                             data-testid="DIALOGUE_CONFIRM_ID" | ||||
|                             type="submit" | ||||
|                             variant="contained" | ||||
|                             color="primary" | ||||
|  | ||||
| @ -304,6 +304,7 @@ export const VariantForm = ({ | ||||
|             > | ||||
|                 <div> | ||||
|                     <IconButton | ||||
|                         data-testid={`VARIANT_DELETE_BUTTON_${variant.name}`} | ||||
|                         onClick={() => removeVariant(variant.id)} | ||||
|                         disabled={isProtectedVariant(variant)} | ||||
|                     > | ||||
| @ -318,6 +319,7 @@ export const VariantForm = ({ | ||||
|                         This will be used to identify the variant in your code | ||||
|                     </StyledSubLabel> | ||||
|                     <StyledInput | ||||
|                         data-testid="VARIANT_NAME_INPUT" | ||||
|                         autoFocus | ||||
|                         label="Variant name" | ||||
|                         error={Boolean(errors.name)} | ||||
| @ -336,6 +338,7 @@ export const VariantForm = ({ | ||||
|                                 label="Custom percentage" | ||||
|                                 control={ | ||||
|                                     <Switch | ||||
|                                         data-testid="VARIANT_WEIGHT_CHECK" | ||||
|                                         checked={customPercentage} | ||||
|                                         onChange={e => | ||||
|                                             setCustomPercentage( | ||||
| @ -346,6 +349,7 @@ export const VariantForm = ({ | ||||
|                                 } | ||||
|                             /> | ||||
|                             <StyledWeightInput | ||||
|                                 data-testid="VARIANT_WEIGHT_INPUT" | ||||
|                                 type="number" | ||||
|                                 label="Variant weight" | ||||
|                                 error={Boolean(errors.percentage)} | ||||
|  | ||||
| @ -318,6 +318,7 @@ export const FeatureEnvironmentVariants = () => { | ||||
|                                 )} | ||||
|                                 show={ | ||||
|                                     <PermissionIconButton | ||||
|                                         data-testid="EDIT_VARIANTS_BUTTON" | ||||
|                                         onClick={() => | ||||
|                                             editVariants(environment) | ||||
|                                         } | ||||
| @ -332,6 +333,7 @@ export const FeatureEnvironmentVariants = () => { | ||||
|                                 } | ||||
|                                 elseShow={ | ||||
|                                     <PermissionButton | ||||
|                                         data-testid="ADD_VARIANT_BUTTON" | ||||
|                                         onClick={() => | ||||
|                                             editVariants(environment) | ||||
|                                         } | ||||
|  | ||||
| @ -1,18 +1,10 @@ | ||||
| import { FeatureVariantsList } from './FeatureVariantsList/FeatureVariantsList'; | ||||
| import { usePageTitle } from 'hooks/usePageTitle'; | ||||
| import { FeatureEnvironmentVariants } from './FeatureEnvironmentVariants/FeatureEnvironmentVariants'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| 
 | ||||
| const FeatureVariants = () => { | ||||
|     usePageTitle('Variants'); | ||||
| 
 | ||||
|     const { uiConfig } = useUiConfig(); | ||||
| 
 | ||||
|     if (uiConfig.flags.variantsPerEnvironment) { | ||||
|         return <FeatureEnvironmentVariants />; | ||||
|     } | ||||
| 
 | ||||
|     return <FeatureVariantsList />; | ||||
|     return <FeatureEnvironmentVariants />; | ||||
| }; | ||||
| 
 | ||||
| export default FeatureVariants; | ||||
|  | ||||
| @ -4,7 +4,6 @@ import { emptyFeature } from './emptyFeature'; | ||||
| import handleErrorResponses from '../httpErrorResponseHandler'; | ||||
| import { formatApiPath } from 'utils/formatPath'; | ||||
| import { IFeatureToggle } from 'interfaces/featureToggle'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| 
 | ||||
| export interface IUseFeatureOutput { | ||||
|     feature: IFeatureToggle; | ||||
| @ -26,14 +25,9 @@ export const useFeature = ( | ||||
| ): IUseFeatureOutput => { | ||||
|     const path = formatFeatureApiPath(projectId, featureId); | ||||
| 
 | ||||
|     const { uiConfig } = useUiConfig(); | ||||
|     const { | ||||
|         flags: { variantsPerEnvironment }, | ||||
|     } = uiConfig; | ||||
| 
 | ||||
|     const { data, error, mutate } = useSWR<IFeatureResponse>( | ||||
|         ['useFeature', path, variantsPerEnvironment], | ||||
|         () => featureFetcher(path, variantsPerEnvironment), | ||||
|         ['useFeature', path], | ||||
|         () => featureFetcher(path), | ||||
|         options | ||||
|     ); | ||||
| 
 | ||||
| @ -51,14 +45,9 @@ export const useFeature = ( | ||||
| }; | ||||
| 
 | ||||
| export const featureFetcher = async ( | ||||
|     path: string, | ||||
|     variantsPerEnvironment?: boolean | ||||
|     path: string | ||||
| ): Promise<IFeatureResponse> => { | ||||
|     const variantEnvironments = variantsPerEnvironment | ||||
|         ? '?variantEnvironments=true' | ||||
|         : ''; | ||||
| 
 | ||||
|     const res = await fetch(path + variantEnvironments); | ||||
|     const res = await fetch(path); | ||||
| 
 | ||||
|     if (res.status === 404) { | ||||
|         return { status: 404 }; | ||||
| @ -79,6 +68,6 @@ export const formatFeatureApiPath = ( | ||||
|     featureId: string | ||||
| ): string => { | ||||
|     return formatApiPath( | ||||
|         `api/admin/projects/${projectId}/features/${featureId}` | ||||
|         `api/admin/projects/${projectId}/features/${featureId}?variantEnvironments=true` | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -38,7 +38,6 @@ export interface IFlags { | ||||
|     UG?: boolean; | ||||
|     ENABLE_DARK_MODE_SUPPORT?: boolean; | ||||
|     embedProxyFrontend?: boolean; | ||||
|     variantsPerEnvironment?: boolean; | ||||
|     networkView?: boolean; | ||||
|     maintenance?: boolean; | ||||
|     maintenanceMode?: boolean; | ||||
|  | ||||
| @ -84,7 +84,6 @@ exports[`should create default config 1`] = ` | ||||
|       "responseTimeWithAppName": false, | ||||
|       "showProjectApiAccess": false, | ||||
|       "strictSchemaValidation": false, | ||||
|       "variantsPerEnvironment": false, | ||||
|     }, | ||||
|   }, | ||||
|   "flagResolver": FlagResolver { | ||||
| @ -106,7 +105,6 @@ exports[`should create default config 1`] = ` | ||||
|       "responseTimeWithAppName": false, | ||||
|       "showProjectApiAccess": false, | ||||
|       "strictSchemaValidation": false, | ||||
|       "variantsPerEnvironment": false, | ||||
|     }, | ||||
|     "externalResolver": { | ||||
|       "isEnabled": [Function], | ||||
|  | ||||
| @ -97,13 +97,6 @@ export default class EnvironmentService { | ||||
|                 environment, | ||||
|                 projectId, | ||||
|             ); | ||||
| 
 | ||||
|             if (!this.flagResolver.isEnabled('variantsPerEnvironment')) { | ||||
|                 await this.featureEnvironmentStore.clonePreviousVariants( | ||||
|                     environment, | ||||
|                     projectId, | ||||
|                 ); | ||||
|             } | ||||
|         } catch (e) { | ||||
|             if (e.code === UNIQUE_CONSTRAINT_VIOLATION) { | ||||
|                 throw new NameExistsError( | ||||
|  | ||||
| @ -27,7 +27,6 @@ function getSetup() { | ||||
| } | ||||
| 
 | ||||
| async function setupV3VariantsCompatibilityScenario( | ||||
|     variantsPerEnvironment: boolean, | ||||
|     envs = [ | ||||
|         { name: 'env-2', enabled: true }, | ||||
|         { name: 'env-3', enabled: true }, | ||||
| @ -55,9 +54,7 @@ async function setupV3VariantsCompatibilityScenario( | ||||
|             env.name, | ||||
|             [ | ||||
|                 { | ||||
|                     name: variantsPerEnvironment | ||||
|                         ? `${env.name}-variant` | ||||
|                         : 'variant-name', | ||||
|                     name: `${env.name}-variant`, | ||||
|                     stickiness: 'default', | ||||
|                     weight: 1000, | ||||
|                     weightType: 'variable', | ||||
| @ -69,7 +66,7 @@ async function setupV3VariantsCompatibilityScenario( | ||||
|         stateService: new StateService(stores, { | ||||
|             getLogger, | ||||
|             flagResolver: { | ||||
|                 isEnabled: () => variantsPerEnvironment, | ||||
|                 isEnabled: () => true, | ||||
|                 getAll: () => ({}), | ||||
|             }, | ||||
|         }), | ||||
| @ -622,88 +619,8 @@ test('exporting to new format works', async () => { | ||||
|     expect(exported.featureStrategies).toHaveLength(1); | ||||
| }); | ||||
| 
 | ||||
| test('exporting with no environments should fail', async () => { | ||||
|     const { stateService, stores } = await setupV3VariantsCompatibilityScenario( | ||||
|         false, | ||||
|     ); | ||||
|     await stores.environmentStore.deleteAll(); | ||||
| 
 | ||||
|     expect(stateService.export({})).rejects.toThrowError(); | ||||
| }); | ||||
| 
 | ||||
| // This test should be removed as soon as variants per environment is GA
 | ||||
| test('exporting variants to v3 format should pick variants from the correct env', async () => { | ||||
|     const { stateService } = await setupV3VariantsCompatibilityScenario(false); | ||||
| 
 | ||||
|     const exported = await stateService.export({}); | ||||
|     expect(exported.features).toHaveLength(1); | ||||
| 
 | ||||
|     expect(exported.features[0].variants).toStrictEqual([ | ||||
|         { | ||||
|             name: 'variant-name', | ||||
|             stickiness: 'default', | ||||
|             weight: 1000, | ||||
|             weightType: 'variable', | ||||
|         }, | ||||
|     ]); | ||||
|     exported.featureEnvironments.forEach((fe) => | ||||
|         expect(fe.variants).toBeUndefined(), | ||||
|     ); | ||||
|     expect(exported.environments).toHaveLength(3); | ||||
| }); | ||||
| 
 | ||||
| // This test should be removed as soon as variants per environment is GA
 | ||||
| test('exporting variants to v3 format when some envs are disabled should export variants', async () => { | ||||
|     const { stateService } = await setupV3VariantsCompatibilityScenario(false, [ | ||||
|         { name: 'env-2', enabled: false }, | ||||
|         { name: 'env-3', enabled: false }, | ||||
|         { name: 'env-1', enabled: true }, | ||||
|     ]); | ||||
| 
 | ||||
|     const exported = await stateService.export({}); | ||||
|     expect(exported.features).toHaveLength(1); | ||||
| 
 | ||||
|     expect(exported.features[0].variants).toStrictEqual([ | ||||
|         { | ||||
|             name: 'variant-name', | ||||
|             stickiness: 'default', | ||||
|             weight: 1000, | ||||
|             weightType: 'variable', | ||||
|         }, | ||||
|     ]); | ||||
|     exported.featureEnvironments.forEach((fe) => | ||||
|         expect(fe.variants).toBeUndefined(), | ||||
|     ); | ||||
|     expect(exported.environments).toHaveLength(3); | ||||
| }); | ||||
| 
 | ||||
| // This test should be removed as soon as variants per environment is GA
 | ||||
| test('exporting variants to v3 format when all envs are disabled should export variants', async () => { | ||||
|     const { stateService } = await setupV3VariantsCompatibilityScenario(false, [ | ||||
|         { name: 'env-2', enabled: false }, | ||||
|         { name: 'env-3', enabled: false }, | ||||
|         { name: 'env-1', enabled: false }, | ||||
|     ]); | ||||
| 
 | ||||
|     const exported = await stateService.export({}); | ||||
|     expect(exported.features).toHaveLength(1); | ||||
| 
 | ||||
|     expect(exported.features[0].variants).toStrictEqual([ | ||||
|         { | ||||
|             name: 'variant-name', | ||||
|             stickiness: 'default', | ||||
|             weight: 1000, | ||||
|             weightType: 'variable', | ||||
|         }, | ||||
|     ]); | ||||
|     exported.featureEnvironments.forEach((fe) => | ||||
|         expect(fe.variants).toBeUndefined(), | ||||
|     ); | ||||
|     expect(exported.environments).toHaveLength(3); | ||||
| }); | ||||
| 
 | ||||
| test('exporting variants to v4 format should not include variants in features', async () => { | ||||
|     const { stateService } = await setupV3VariantsCompatibilityScenario(true); | ||||
|     const { stateService } = await setupV3VariantsCompatibilityScenario(); | ||||
|     const exported = await stateService.export({}); | ||||
| 
 | ||||
|     expect(exported.features).toHaveLength(1); | ||||
|  | ||||
| @ -703,61 +703,7 @@ export default class StateService { | ||||
|         environments: IEnvironment[]; | ||||
|         featureEnvironments: IFeatureEnvironment[]; | ||||
|     }> { | ||||
|         if (this.flagResolver.isEnabled('variantsPerEnvironment')) { | ||||
|             return this.exportV4(opts); | ||||
|         } | ||||
|         // adapt v4 to v3. We need includeEnvironments set to true to filter the
 | ||||
|         // best environment from where we'll pick variants (cause now they are stored
 | ||||
|         // per environment despite being displayed as if they belong to the feature)
 | ||||
|         const v4 = await this.exportV4({ ...opts, includeEnvironments: true }); | ||||
|         // undefined defaults to true
 | ||||
|         if (opts.includeFeatureToggles !== false) { | ||||
|             const enabledEnvironments = v4.environments.filter( | ||||
|                 (env) => env.enabled !== false, | ||||
|             ); | ||||
| 
 | ||||
|             const featureAndEnvs = v4.featureEnvironments.map((fE) => { | ||||
|                 const envDetails = enabledEnvironments.find( | ||||
|                     (env) => fE.environment === env.name, | ||||
|                 ); | ||||
|                 return { ...fE, ...envDetails, active: fE.enabled }; | ||||
|             }); | ||||
|             v4.features = v4.features.map((f) => { | ||||
|                 const variants = featureAndEnvs | ||||
|                     .sort((e1, e2) => { | ||||
|                         if (e1.active !== e2.active) { | ||||
|                             return e1.active ? -1 : 1; | ||||
|                         } | ||||
|                         if ( | ||||
|                             e1.type !== 'production' || | ||||
|                             e2.type !== 'production' | ||||
|                         ) { | ||||
|                             if (e1.type === 'production') { | ||||
|                                 return -1; | ||||
|                             } else if (e2.type === 'production') { | ||||
|                                 return 1; | ||||
|                             } | ||||
|                         } | ||||
|                         return e1.sortOrder - e2.sortOrder; | ||||
|                     }) | ||||
|                     .find((fe) => fe.featureName === f.name)?.variants; | ||||
|                 return { ...f, variants }; | ||||
|             }); | ||||
|             v4.featureEnvironments = v4.featureEnvironments.map((fe) => { | ||||
|                 delete fe.variants; | ||||
|                 return fe; | ||||
|             }); | ||||
|         } | ||||
|         // only if explicitly set to false (i.e. undefined defaults to true)
 | ||||
|         if (opts.includeEnvironments === false) { | ||||
|             delete v4.environments; | ||||
|         } else { | ||||
|             if (v4.environments.length === 0) { | ||||
|                 throw Error('Unable to export without enabled environments'); | ||||
|             } | ||||
|         } | ||||
|         v4.version = 3; | ||||
|         return v4; | ||||
|         return this.exportV4(opts); | ||||
|     } | ||||
| 
 | ||||
|     async exportV4({ | ||||
|  | ||||
| @ -30,10 +30,6 @@ const flags = { | ||||
|         process.env.UNLEASH_EXPERIMENTAL_PROXY_RETURN_ALL_TOGGLES, | ||||
|         false, | ||||
|     ), | ||||
|     variantsPerEnvironment: parseEnvVarBoolean( | ||||
|         process.env.UNLEASH_EXPERIMENTAL_VARIANTS_PER_ENVIRONMENT, | ||||
|         false, | ||||
|     ), | ||||
|     networkView: parseEnvVarBoolean( | ||||
|         process.env.UNLEASH_EXPERIMENTAL_NETWORK_VIEW, | ||||
|         false, | ||||
|  | ||||
| @ -38,7 +38,6 @@ process.nextTick(async () => { | ||||
|                         embedProxyFrontend: true, | ||||
|                         anonymiseEventLog: false, | ||||
|                         responseTimeWithAppName: true, | ||||
|                         variantsPerEnvironment: true, | ||||
|                         maintenance: true, | ||||
|                         featuresExportImport: true, | ||||
|                         newProjectOverview: true, | ||||
|  | ||||
| @ -26,7 +26,6 @@ export function createTestConfig(config?: IUnleashOptions): IUnleashConfig { | ||||
|             flags: { | ||||
|                 embedProxy: true, | ||||
|                 embedProxyFrontend: true, | ||||
|                 variantsPerEnvironment: true, | ||||
|             }, | ||||
|         }, | ||||
|     }; | ||||
|  | ||||
| @ -4,9 +4,6 @@ import getLogger from '../../../fixtures/no-logger'; | ||||
| import { DEFAULT_ENV } from '../../../../lib/util/constants'; | ||||
| import { collectIds } from '../../../../lib/util/collect-ids'; | ||||
| import { ApiTokenType } from '../../../../lib/types/models/api-token'; | ||||
| import variantsv3 from '../../../examples/variantsexport_v3.json'; | ||||
| import v3WithDefaultDisabled from '../../../examples/exported3-with-default-disabled.json'; | ||||
| import { StateService } from '../../../../lib/services'; | ||||
| 
 | ||||
| const importData = require('../../../examples/import.json'); | ||||
| 
 | ||||
| @ -435,66 +432,3 @@ test(`should not show environment on feature toggle, when environment is disable | ||||
|     expect(result[1].name).toBe('production'); | ||||
|     expect(result[1].enabled).toBeFalsy(); | ||||
| }); | ||||
| 
 | ||||
| test(`should handle v3 export with variants in features`, async () => { | ||||
|     app.services.stateService = new StateService(db.stores, { | ||||
|         getLogger, | ||||
|         flagResolver: { | ||||
|             isEnabled: () => false, | ||||
|             getAll: () => ({}), | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     await app.request | ||||
|         .post('/api/admin/state/import?drop=true') | ||||
|         .attach('file', 'src/test/examples/variantsexport_v3.json') | ||||
|         .expect(202); | ||||
| 
 | ||||
|     const exported = await app.services.stateService.export({}); | ||||
|     let exportedFeatures = exported.features | ||||
|         .map((f) => { | ||||
|             delete f.createdAt; | ||||
|             return f; | ||||
|         }) | ||||
|         .sort(); | ||||
|     let importedFeatures = variantsv3.features | ||||
|         .map((f) => { | ||||
|             delete f.createdAt; | ||||
|             return f; | ||||
|         }) | ||||
|         .sort(); | ||||
|     expect(exportedFeatures).toStrictEqual(importedFeatures); | ||||
| }); | ||||
| 
 | ||||
| test(`should handle v3 export with variants in features and only 1 env`, async () => { | ||||
|     app.services.stateService = new StateService(db.stores, { | ||||
|         getLogger, | ||||
|         flagResolver: { | ||||
|             isEnabled: () => false, | ||||
|             getAll: () => ({}), | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     await app.request | ||||
|         .post('/api/admin/state/import?drop=true') | ||||
|         .attach( | ||||
|             'file', | ||||
|             'src/test/examples/exported3-with-default-disabled.json', | ||||
|         ) | ||||
|         .expect(202); | ||||
| 
 | ||||
|     const exported = await app.services.stateService.export({}); | ||||
|     let exportedFeatures = exported.features | ||||
|         .map((f) => { | ||||
|             delete f.createdAt; | ||||
|             return f; | ||||
|         }) | ||||
|         .sort(); | ||||
|     let importedFeatures = v3WithDefaultDisabled.features | ||||
|         .map((f) => { | ||||
|             delete f.createdAt; | ||||
|             return f; | ||||
|         }) | ||||
|         .sort(); | ||||
|     expect(exportedFeatures).toStrictEqual(importedFeatures); | ||||
| }); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user