mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: constraints limit in a strategy (#7554)
This commit is contained in:
		
							parent
							
								
									ef80d7f81e
								
							
						
					
					
						commit
						7ca2ace0bc
					
				@ -200,6 +200,7 @@ exports[`should create default config 1`] = `
 | 
			
		||||
    "actionSetsPerProject": 5,
 | 
			
		||||
    "apiTokens": 2000,
 | 
			
		||||
    "constraintValues": 250,
 | 
			
		||||
    "constraints": 30,
 | 
			
		||||
    "environments": 50,
 | 
			
		||||
    "featureEnvironmentStrategies": 30,
 | 
			
		||||
    "featureFlags": 5000,
 | 
			
		||||
 | 
			
		||||
@ -663,6 +663,10 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig {
 | 
			
		||||
                options?.resourceLimits?.constraintValues || 250,
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
        constraints: Math.max(
 | 
			
		||||
            0,
 | 
			
		||||
            parseEnvVarNumber(process.env.UNLEASH_CONSTRAINTS_LIMIT, 30),
 | 
			
		||||
        ),
 | 
			
		||||
        environments: parseEnvVarNumber(
 | 
			
		||||
            process.env.UNLEASH_ENVIRONMENTS_LIMIT,
 | 
			
		||||
            50,
 | 
			
		||||
 | 
			
		||||
@ -387,19 +387,24 @@ class FeatureToggleService {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    validateConstraintValuesLimit(updatedConstrains: IConstraint[]) {
 | 
			
		||||
    validateConstraintsLimit(updatedConstrains: IConstraint[]) {
 | 
			
		||||
        if (!this.flagResolver.isEnabled('resourceLimits')) return;
 | 
			
		||||
 | 
			
		||||
        const limit = this.resourceLimits.constraintValues;
 | 
			
		||||
        const constraintsLimit = this.resourceLimits.constraints;
 | 
			
		||||
        if (updatedConstrains.length > constraintsLimit) {
 | 
			
		||||
            throw new ExceedsLimitError(`constraints`, constraintsLimit);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const constraintValuesLimit = this.resourceLimits.constraintValues;
 | 
			
		||||
        const constraintOverLimit = updatedConstrains.find(
 | 
			
		||||
            (constraint) =>
 | 
			
		||||
                Array.isArray(constraint.values) &&
 | 
			
		||||
                constraint.values?.length > limit,
 | 
			
		||||
                constraint.values?.length > constraintValuesLimit,
 | 
			
		||||
        );
 | 
			
		||||
        if (constraintOverLimit) {
 | 
			
		||||
            throw new ExceedsLimitError(
 | 
			
		||||
                `content values for ${constraintOverLimit.contextName}`,
 | 
			
		||||
                limit,
 | 
			
		||||
                constraintValuesLimit,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -649,7 +654,7 @@ class FeatureToggleService {
 | 
			
		||||
            strategyConfig.constraints &&
 | 
			
		||||
            strategyConfig.constraints.length > 0
 | 
			
		||||
        ) {
 | 
			
		||||
            this.validateConstraintValuesLimit(strategyConfig.constraints);
 | 
			
		||||
            this.validateConstraintsLimit(strategyConfig.constraints);
 | 
			
		||||
            strategyConfig.constraints = await this.validateConstraints(
 | 
			
		||||
                strategyConfig.constraints,
 | 
			
		||||
            );
 | 
			
		||||
@ -808,7 +813,7 @@ class FeatureToggleService {
 | 
			
		||||
 | 
			
		||||
        if (existingStrategy.id === id) {
 | 
			
		||||
            if (updates.constraints && updates.constraints.length > 0) {
 | 
			
		||||
                this.validateConstraintValuesLimit(updates.constraints);
 | 
			
		||||
                this.validateConstraintsLimit(updates.constraints);
 | 
			
		||||
                updates.constraints = await this.validateConstraints(
 | 
			
		||||
                    updates.constraints,
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
@ -47,6 +47,50 @@ describe('Strategy limits', () => {
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Should not allow to exceed constraints limit', async () => {
 | 
			
		||||
        const LIMIT = 1;
 | 
			
		||||
        const { featureToggleService, featureToggleStore } =
 | 
			
		||||
            createFakeFeatureToggleService({
 | 
			
		||||
                getLogger,
 | 
			
		||||
                flagResolver: alwaysOnFlagResolver,
 | 
			
		||||
                resourceLimits: {
 | 
			
		||||
                    constraints: LIMIT,
 | 
			
		||||
                },
 | 
			
		||||
            } as unknown as IUnleashConfig);
 | 
			
		||||
 | 
			
		||||
        const addStrategy = (constraints: IConstraint[]) =>
 | 
			
		||||
            featureToggleService.unprotectedCreateStrategy(
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'default',
 | 
			
		||||
                    featureName: 'feature',
 | 
			
		||||
                    constraints: constraints,
 | 
			
		||||
                } as IStrategyConfig,
 | 
			
		||||
                { projectId: 'default', featureName: 'feature' } as any,
 | 
			
		||||
                {} as IAuditUser,
 | 
			
		||||
            );
 | 
			
		||||
        await featureToggleStore.create('default', {
 | 
			
		||||
            name: 'feature',
 | 
			
		||||
            createdByUserId: 1,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        await expect(
 | 
			
		||||
            addStrategy([
 | 
			
		||||
                {
 | 
			
		||||
                    values: ['1'],
 | 
			
		||||
                    operator: 'IN',
 | 
			
		||||
                    contextName: 'accountId',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    values: ['2'],
 | 
			
		||||
                    operator: 'IN',
 | 
			
		||||
                    contextName: 'accountId',
 | 
			
		||||
                },
 | 
			
		||||
            ]),
 | 
			
		||||
        ).rejects.toThrow(
 | 
			
		||||
            "Failed to create constraints. You can't create more than the established limit of 1",
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Should not allow to exceed constraint values limit', async () => {
 | 
			
		||||
        const LIMIT = 3;
 | 
			
		||||
        const { featureToggleService, featureToggleStore } =
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ export const resourceLimitsSchema = {
 | 
			
		||||
        'apiTokens',
 | 
			
		||||
        'segments',
 | 
			
		||||
        'featureFlags',
 | 
			
		||||
        'constraints',
 | 
			
		||||
    ],
 | 
			
		||||
    additionalProperties: false,
 | 
			
		||||
    properties: {
 | 
			
		||||
@ -80,6 +81,12 @@ export const resourceLimitsSchema = {
 | 
			
		||||
            description:
 | 
			
		||||
                'The maximum number of values for a single constraint.',
 | 
			
		||||
        },
 | 
			
		||||
        constraints: {
 | 
			
		||||
            type: 'integer',
 | 
			
		||||
            example: 30,
 | 
			
		||||
            description:
 | 
			
		||||
                'The maximum number of constraints in a single strategy.',
 | 
			
		||||
        },
 | 
			
		||||
        environments: {
 | 
			
		||||
            type: 'integer',
 | 
			
		||||
            minimum: 1,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user