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,
|
"featureEnvironmentStrategies": 30,
|
||||||
"projects": 500,
|
"projects": 500,
|
||||||
"segmentValues": 1000,
|
"segmentValues": 1000,
|
||||||
|
"segments": 300,
|
||||||
"signalEndpoints": 5,
|
"signalEndpoints": 5,
|
||||||
"signalTokensPerEndpoint": 5,
|
"signalTokensPerEndpoint": 5,
|
||||||
"strategySegments": 5,
|
"strategySegments": 5,
|
||||||
|
@ -675,6 +675,10 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig {
|
|||||||
0,
|
0,
|
||||||
parseEnvVarNumber(process.env.UNLEASH_API_TOKENS_LIMIT, 2000),
|
parseEnvVarNumber(process.env.UNLEASH_API_TOKENS_LIMIT, 2000),
|
||||||
),
|
),
|
||||||
|
segments: Math.max(
|
||||||
|
0,
|
||||||
|
parseEnvVarNumber(process.env.UNLEASH_SEGMENTS_LIMIT, 300),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
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 { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType';
|
||||||
import type EventService from '../events/event-service';
|
import type EventService from '../events/event-service';
|
||||||
import type { IChangeRequestSegmentUsageReadModel } from '../change-request-segment-usage-service/change-request-segment-usage-read-model';
|
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 {
|
export class SegmentService implements ISegmentService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
@ -45,6 +47,8 @@ export class SegmentService implements ISegmentService {
|
|||||||
|
|
||||||
private privateProjectChecker: IPrivateProjectChecker;
|
private privateProjectChecker: IPrivateProjectChecker;
|
||||||
|
|
||||||
|
private resourceLimits: ResourceLimitsSchema;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{
|
{
|
||||||
segmentStore,
|
segmentStore,
|
||||||
@ -65,6 +69,7 @@ export class SegmentService implements ISegmentService {
|
|||||||
this.privateProjectChecker = privateProjectChecker;
|
this.privateProjectChecker = privateProjectChecker;
|
||||||
this.logger = config.getLogger('services/segment-service.ts');
|
this.logger = config.getLogger('services/segment-service.ts');
|
||||||
this.flagResolver = config.flagResolver;
|
this.flagResolver = config.flagResolver;
|
||||||
|
this.resourceLimits = config.resourceLimits;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +128,21 @@ export class SegmentService implements ISegmentService {
|
|||||||
return strategies.length > 0 || changeRequestStrategies.length > 0;
|
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> {
|
async create(data: unknown, auditUser: IAuditUser): Promise<ISegment> {
|
||||||
|
await this.validateSegmentLimit();
|
||||||
|
|
||||||
const input = await segmentSchema.validateAsync(data);
|
const input = await segmentSchema.validateAsync(data);
|
||||||
this.validateSegmentValuesLimit(input);
|
this.validateSegmentValuesLimit(input);
|
||||||
await this.validateName(input.name);
|
await this.validateName(input.name);
|
||||||
|
@ -17,6 +17,8 @@ export const resourceLimitsSchema = {
|
|||||||
'constraintValues',
|
'constraintValues',
|
||||||
'environments',
|
'environments',
|
||||||
'projects',
|
'projects',
|
||||||
|
'apiTokens',
|
||||||
|
'segments',
|
||||||
],
|
],
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
properties: {
|
properties: {
|
||||||
@ -96,6 +98,11 @@ export const resourceLimitsSchema = {
|
|||||||
example: 500,
|
example: 500,
|
||||||
description: 'The maximum number of projects allowed.',
|
description: 'The maximum number of projects allowed.',
|
||||||
},
|
},
|
||||||
|
segments: {
|
||||||
|
type: 'integer',
|
||||||
|
example: 300,
|
||||||
|
description: 'The maximum number of segments allowed.',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: {},
|
components: {},
|
||||||
} as const;
|
} 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';
|
import type { IFeatureStrategySegment, ISegment } from '../../lib/types/model';
|
||||||
|
|
||||||
export default class FakeSegmentStore implements ISegmentStore {
|
export default class FakeSegmentStore implements ISegmentStore {
|
||||||
count(): Promise<number> {
|
segments: ISegment[] = [];
|
||||||
return Promise.resolve(0);
|
currentId: number = 0;
|
||||||
|
|
||||||
|
async count(): Promise<number> {
|
||||||
|
return this.segments.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
create(): Promise<ISegment> {
|
async create(segment: Omit<ISegment, 'id'>): Promise<ISegment> {
|
||||||
throw new Error('Method not implemented.');
|
const newSegment = { ...segment, id: this.currentId };
|
||||||
|
this.currentId = this.currentId + 1;
|
||||||
|
this.segments.push(newSegment);
|
||||||
|
|
||||||
|
return newSegment;
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(): Promise<void> {
|
async delete(): Promise<void> {
|
||||||
@ -50,8 +57,8 @@ export default class FakeSegmentStore implements ISegmentStore {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async existsByName(): Promise<boolean> {
|
async existsByName(name: string): Promise<boolean> {
|
||||||
throw new Error('Method not implemented.');
|
return this.segments.some((segment) => segment.name === name);
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {}
|
destroy(): void {}
|
||||||
|
Loading…
Reference in New Issue
Block a user