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:
parent
5832fc7d81
commit
72615cc6d5
@ -202,6 +202,7 @@ exports[`should create default config 1`] = `
|
||||
"featureEnvironmentStrategies": 30,
|
||||
"projects": 500,
|
||||
"segmentValues": 1000,
|
||||
"segments": 300,
|
||||
"signalEndpoints": 5,
|
||||
"signalTokensPerEndpoint": 5,
|
||||
"strategySegments": 5,
|
||||
|
@ -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 {
|
||||
|
29
src/lib/features/segment/segment-service.limit.test.ts
Normal file
29
src/lib/features/segment/segment-service.limit.test.ts
Normal 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.",
|
||||
);
|
||||
});
|
@ -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);
|
||||
|
@ -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;
|
||||
|
19
src/test/fixtures/fake-segment-store.ts
vendored
19
src/test/fixtures/fake-segment-store.ts
vendored
@ -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 {}
|
||||
|
Loading…
Reference in New Issue
Block a user