1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

feat: segments limit (#7524)

This commit is contained in:
Mateusz Kwasniewski 2024-07-03 10:41:56 +02:00 committed by GitHub
parent 5832fc7d81
commit 72615cc6d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 73 additions and 6 deletions

View File

@ -202,6 +202,7 @@ exports[`should create default config 1`] = `
"featureEnvironmentStrategies": 30,
"projects": 500,
"segmentValues": 1000,
"segments": 300,
"signalEndpoints": 5,
"signalTokensPerEndpoint": 5,
"strategySegments": 5,

View File

@ -675,6 +675,10 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig {
0,
parseEnvVarNumber(process.env.UNLEASH_API_TOKENS_LIMIT, 2000),
),
segments: Math.max(
0,
parseEnvVarNumber(process.env.UNLEASH_SEGMENTS_LIMIT, 300),
),
};
return {

View File

@ -0,0 +1,29 @@
import type { IAuditUser, IFlagResolver, IUnleashConfig } from '../../types';
import getLogger from '../../../test/fixtures/no-logger';
import { createFakeSegmentService } from './createSegmentService';
const alwaysOnFlagResolver = {
isEnabled() {
return true;
},
} as unknown as IFlagResolver;
test('Should not allow to exceed segment limit', async () => {
const LIMIT = 1;
const segmentService = createFakeSegmentService({
getLogger,
flagResolver: alwaysOnFlagResolver,
resourceLimits: {
segments: LIMIT,
},
} as unknown as IUnleashConfig);
const createSegment = (name: string) =>
segmentService.create({ name, constraints: [] }, {} as IAuditUser);
await createSegment('segmentA');
await expect(() => createSegment('segmentB')).rejects.toThrow(
"Failed to create segment. You can't create more than the established limit of 1.",
);
});

View File

@ -25,6 +25,8 @@ import type { IChangeRequestAccessReadModel } from '../change-request-access-ser
import type { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType';
import type EventService from '../events/event-service';
import type { IChangeRequestSegmentUsageReadModel } from '../change-request-segment-usage-service/change-request-segment-usage-read-model';
import type { ResourceLimitsSchema } from '../../openapi';
import { ExceedsLimitError } from '../../error/exceeds-limit-error';
export class SegmentService implements ISegmentService {
private logger: Logger;
@ -45,6 +47,8 @@ export class SegmentService implements ISegmentService {
private privateProjectChecker: IPrivateProjectChecker;
private resourceLimits: ResourceLimitsSchema;
constructor(
{
segmentStore,
@ -65,6 +69,7 @@ export class SegmentService implements ISegmentService {
this.privateProjectChecker = privateProjectChecker;
this.logger = config.getLogger('services/segment-service.ts');
this.flagResolver = config.flagResolver;
this.resourceLimits = config.resourceLimits;
this.config = config;
}
@ -123,7 +128,21 @@ export class SegmentService implements ISegmentService {
return strategies.length > 0 || changeRequestStrategies.length > 0;
}
async validateSegmentLimit() {
if (!this.flagResolver.isEnabled('resourceLimits')) return;
const limit = this.resourceLimits.segments;
const segmentCount = await this.segmentStore.count();
if (segmentCount >= limit) {
throw new ExceedsLimitError('segment', limit);
}
}
async create(data: unknown, auditUser: IAuditUser): Promise<ISegment> {
await this.validateSegmentLimit();
const input = await segmentSchema.validateAsync(data);
this.validateSegmentValuesLimit(input);
await this.validateName(input.name);

View File

@ -17,6 +17,8 @@ export const resourceLimitsSchema = {
'constraintValues',
'environments',
'projects',
'apiTokens',
'segments',
],
additionalProperties: false,
properties: {
@ -96,6 +98,11 @@ export const resourceLimitsSchema = {
example: 500,
description: 'The maximum number of projects allowed.',
},
segments: {
type: 'integer',
example: 300,
description: 'The maximum number of segments allowed.',
},
},
components: {},
} as const;

View File

@ -2,12 +2,19 @@ import type { ISegmentStore } from '../../lib/features/segment/segment-store-typ
import type { IFeatureStrategySegment, ISegment } from '../../lib/types/model';
export default class FakeSegmentStore implements ISegmentStore {
count(): Promise<number> {
return Promise.resolve(0);
segments: ISegment[] = [];
currentId: number = 0;
async count(): Promise<number> {
return this.segments.length;
}
create(): Promise<ISegment> {
throw new Error('Method not implemented.');
async create(segment: Omit<ISegment, 'id'>): Promise<ISegment> {
const newSegment = { ...segment, id: this.currentId };
this.currentId = this.currentId + 1;
this.segments.push(newSegment);
return newSegment;
}
async delete(): Promise<void> {
@ -50,8 +57,8 @@ export default class FakeSegmentStore implements ISegmentStore {
return [];
}
async existsByName(): Promise<boolean> {
throw new Error('Method not implemented.');
async existsByName(name: string): Promise<boolean> {
return this.segments.some((segment) => segment.name === name);
}
destroy(): void {}