mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: constraint values limit (#7498)
This commit is contained in:
		
							parent
							
								
									c907199d23
								
							
						
					
					
						commit
						2cfd71f34e
					
				| @ -196,6 +196,7 @@ exports[`should create default config 1`] = ` | ||||
|     "actionSetFilterValues": 25, | ||||
|     "actionSetFilters": 5, | ||||
|     "actionSetsPerProject": 5, | ||||
|     "constraintValues": 250, | ||||
|     "environments": 50, | ||||
|     "featureEnvironmentStrategies": 30, | ||||
|     "segmentValues": 1000, | ||||
|  | ||||
| @ -653,6 +653,10 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig { | ||||
|             process.env.UNLEASH_FEATURE_ENVIRONMENT_STRATEGIES_LIMIT, | ||||
|             30, | ||||
|         ), | ||||
|         constraintValues: parseEnvVarNumber( | ||||
|             process.env.UNLEASH_CONSTRAINT_VALUES_LIMIT, | ||||
|             250, | ||||
|         ), | ||||
|         environments: parseEnvVarNumber( | ||||
|             process.env.UNLEASH_ENVIRONMENTS_LIMIT, | ||||
|             50, | ||||
|  | ||||
| @ -366,15 +366,14 @@ class FeatureToggleService { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async validateStrategyLimit( | ||||
|         featureEnv: { | ||||
|             projectId: string; | ||||
|             environment: string; | ||||
|             featureName: string; | ||||
|         }, | ||||
|         limit: number, | ||||
|     ) { | ||||
|     async validateStrategyLimit(featureEnv: { | ||||
|         projectId: string; | ||||
|         environment: string; | ||||
|         featureName: string; | ||||
|     }) { | ||||
|         if (!this.flagResolver.isEnabled('resourceLimits')) return; | ||||
| 
 | ||||
|         const limit = this.resourceLimits.featureEnvironmentStrategies; | ||||
|         const existingCount = ( | ||||
|             await this.featureStrategiesStore.getStrategiesForFeatureEnv( | ||||
|                 featureEnv.projectId, | ||||
| @ -387,6 +386,22 @@ class FeatureToggleService { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     validateContraintValuesLimit(updatedConstrains: IConstraint[]) { | ||||
|         if (!this.flagResolver.isEnabled('resourceLimits')) return; | ||||
| 
 | ||||
|         const limit = this.resourceLimits.constraintValues; | ||||
|         const constraintOverLimit = updatedConstrains.find( | ||||
|             (constraint) => | ||||
|                 Array.isArray(constraint.values) && | ||||
|                 constraint.values?.length > limit, | ||||
|         ); | ||||
|         if (constraintOverLimit) { | ||||
|             throw new BadDataError( | ||||
|                 `Constraint values limit of ${limit} is exceeded for ${constraintOverLimit.contextName}.`, | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async validateStrategyType( | ||||
|         strategyName: string | undefined, | ||||
|     ): Promise<void> { | ||||
| @ -632,6 +647,7 @@ class FeatureToggleService { | ||||
|             strategyConfig.constraints && | ||||
|             strategyConfig.constraints.length > 0 | ||||
|         ) { | ||||
|             this.validateContraintValuesLimit(strategyConfig.constraints); | ||||
|             strategyConfig.constraints = await this.validateConstraints( | ||||
|                 strategyConfig.constraints, | ||||
|             ); | ||||
| @ -653,10 +669,11 @@ class FeatureToggleService { | ||||
|             strategyConfig.variants = fixedVariants; | ||||
|         } | ||||
| 
 | ||||
|         await this.validateStrategyLimit( | ||||
|             { featureName, projectId, environment }, | ||||
|             this.resourceLimits.featureEnvironmentStrategies, | ||||
|         ); | ||||
|         await this.validateStrategyLimit({ | ||||
|             featureName, | ||||
|             projectId, | ||||
|             environment, | ||||
|         }); | ||||
| 
 | ||||
|         try { | ||||
|             const newFeatureStrategy = | ||||
| @ -789,6 +806,7 @@ class FeatureToggleService { | ||||
| 
 | ||||
|         if (existingStrategy.id === id) { | ||||
|             if (updates.constraints && updates.constraints.length > 0) { | ||||
|                 this.validateContraintValuesLimit(updates.constraints); | ||||
|                 updates.constraints = await this.validateConstraints( | ||||
|                     updates.constraints, | ||||
|                 ); | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import { createFakeFeatureToggleService } from '../createFeatureToggleService'; | ||||
| import type { | ||||
|     IAuditUser, | ||||
|     IConstraint, | ||||
|     IFlagResolver, | ||||
|     IStrategyConfig, | ||||
|     IUnleashConfig, | ||||
| @ -43,3 +44,41 @@ test('Should not allow to exceed strategy limit', async () => { | ||||
|         `Strategy limit of ${LIMIT} exceeded`, | ||||
|     ); | ||||
| }); | ||||
| 
 | ||||
| test('Should not allow to exceed constraint values limit', async () => { | ||||
|     const LIMIT = 3; | ||||
|     const { featureToggleService, featureToggleStore } = | ||||
|         createFakeFeatureToggleService({ | ||||
|             getLogger, | ||||
|             flagResolver: alwaysOnFlagResolver, | ||||
|             resourceLimits: { | ||||
|                 constraintValues: LIMIT, | ||||
|             }, | ||||
|         } as unknown as IUnleashConfig); | ||||
| 
 | ||||
|     const addStrategyWithConstraints = (constraints: IConstraint[]) => | ||||
|         featureToggleService.unprotectedCreateStrategy( | ||||
|             { | ||||
|                 name: 'default', | ||||
|                 featureName: 'feature', | ||||
|                 constraints, | ||||
|             } as IStrategyConfig, | ||||
|             { projectId: 'default', featureName: 'feature' } as any, | ||||
|             {} as IAuditUser, | ||||
|         ); | ||||
|     await featureToggleStore.create('default', { | ||||
|         name: 'feature', | ||||
|         createdByUserId: 1, | ||||
|     }); | ||||
|     await expect(() => | ||||
|         addStrategyWithConstraints([ | ||||
|             { | ||||
|                 contextName: 'userId', | ||||
|                 operator: 'IN', | ||||
|                 values: ['1', '2', '3', '4'], | ||||
|             }, | ||||
|         ]), | ||||
|     ).rejects.toThrow( | ||||
|         `Constraint values limit of ${LIMIT} is exceeded for userId`, | ||||
|     ); | ||||
| }); | ||||
|  | ||||
| @ -14,6 +14,7 @@ export const resourceLimitsSchema = { | ||||
|         'signalEndpoints', | ||||
|         'signalTokensPerEndpoint', | ||||
|         'featureEnvironmentStrategies', | ||||
|         'constraintValues', | ||||
|         'environments', | ||||
|     ], | ||||
|     additionalProperties: false, | ||||
| @ -69,6 +70,12 @@ export const resourceLimitsSchema = { | ||||
|             description: | ||||
|                 'The maximum number of feature environment strategies allowed.', | ||||
|         }, | ||||
|         constraintValues: { | ||||
|             type: 'integer', | ||||
|             example: 250, | ||||
|             description: | ||||
|                 'The maximum number of values for a single constraint.', | ||||
|         }, | ||||
|         environments: { | ||||
|             type: 'integer', | ||||
|             minimum: 1, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user