Skip to content

Commit

Permalink
feat: constraint values limit (#7498)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwasniew authored Jul 1, 2024
1 parent c907199 commit 2cfd71f
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 12 deletions.
1 change: 1 addition & 0 deletions src/lib/__snapshots__/create-config.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ exports[`should create default config 1`] = `
"actionSetFilterValues": 25,
"actionSetFilters": 5,
"actionSetsPerProject": 5,
"constraintValues": 250,
"environments": 50,
"featureEnvironmentStrategies": 30,
"segmentValues": 1000,
Expand Down
4 changes: 4 additions & 0 deletions src/lib/create-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
42 changes: 30 additions & 12 deletions src/lib/features/feature-toggle/feature-toggle-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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> {
Expand Down Expand Up @@ -632,6 +647,7 @@ class FeatureToggleService {
strategyConfig.constraints &&
strategyConfig.constraints.length > 0
) {
this.validateContraintValuesLimit(strategyConfig.constraints);
strategyConfig.constraints = await this.validateConstraints(
strategyConfig.constraints,
);
Expand All @@ -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 =
Expand Down Expand Up @@ -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,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createFakeFeatureToggleService } from '../createFeatureToggleService';
import type {
IAuditUser,
IConstraint,
IFlagResolver,
IStrategyConfig,
IUnleashConfig,
Expand Down Expand Up @@ -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`,
);
});
7 changes: 7 additions & 0 deletions src/lib/openapi/spec/resource-limits-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const resourceLimitsSchema = {
'signalEndpoints',
'signalTokensPerEndpoint',
'featureEnvironmentStrategies',
'constraintValues',
'environments',
],
additionalProperties: false,
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 2cfd71f

Please sign in to comment.