mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-14 00:19:16 +01:00
feat: configurable strategies limit (#7488)
This commit is contained in:
parent
94a71798c2
commit
3525928fea
@ -86,6 +86,9 @@ const uiConfigForEnterprise = () =>
|
||||
flags: {
|
||||
changeRequests: true,
|
||||
},
|
||||
resourceLimits: {
|
||||
featureEnvironmentStrategies: 10,
|
||||
},
|
||||
slogan: 'getunleash.io - All rights reserved',
|
||||
name: 'Unleash enterprise',
|
||||
links: [
|
||||
|
@ -8,11 +8,16 @@ import type { IFeatureStrategy } from 'interfaces/strategy';
|
||||
|
||||
const server = testServerSetup();
|
||||
|
||||
const LIMIT = 3;
|
||||
|
||||
const setupApi = () => {
|
||||
testServerRoute(server, '/api/admin/ui-config', {
|
||||
flags: {
|
||||
resourceLimits: true,
|
||||
},
|
||||
resourceLimits: {
|
||||
featureEnvironmentStrategies: LIMIT,
|
||||
},
|
||||
});
|
||||
|
||||
testServerRoute(
|
||||
@ -45,7 +50,7 @@ const environmentWithManyStrategies = {
|
||||
name: 'production',
|
||||
enabled: true,
|
||||
type: 'production',
|
||||
strategies: [...Array(30).keys()].map(() => strategy),
|
||||
strategies: [...Array(LIMIT).keys()].map(() => strategy),
|
||||
};
|
||||
|
||||
test('should allow to add strategy when no strategies', async () => {
|
||||
|
@ -23,6 +23,7 @@ import { FeatureStrategyIcons } from 'component/feature/FeatureStrategy/FeatureS
|
||||
import { useGlobalLocalStorage } from 'hooks/useGlobalLocalStorage';
|
||||
import { Badge } from 'component/common/Badge/Badge';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
|
||||
interface IFeatureOverviewEnvironmentProps {
|
||||
env: IFeatureEnvironment;
|
||||
@ -116,6 +117,19 @@ const StyledButtonContainer = styled('div')(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const useStrategyLimit = (strategyCount: number) => {
|
||||
const resourceLimitsEnabled = useUiFlag('resourceLimits');
|
||||
const { uiConfig } = useUiConfig();
|
||||
const featureEnvironmentStrategiesLimit =
|
||||
uiConfig.resourceLimits?.featureEnvironmentStrategies || 100;
|
||||
const limitReached =
|
||||
resourceLimitsEnabled &&
|
||||
strategyCount >= featureEnvironmentStrategiesLimit;
|
||||
const limitMessage = `Limit of ${featureEnvironmentStrategiesLimit} strategies reached`;
|
||||
|
||||
return { limitReached, limitMessage };
|
||||
};
|
||||
|
||||
const FeatureOverviewEnvironment = ({
|
||||
env,
|
||||
}: IFeatureOverviewEnvironmentProps) => {
|
||||
@ -132,11 +146,10 @@ const FeatureOverviewEnvironment = ({
|
||||
const featureEnvironment = feature?.environments.find(
|
||||
(featureEnvironment) => featureEnvironment.name === env.name,
|
||||
);
|
||||
const resourceLimitsEnabled = useUiFlag('resourceLimits');
|
||||
const limitReached =
|
||||
resourceLimitsEnabled &&
|
||||
Array.isArray(featureEnvironment?.strategies) &&
|
||||
featureEnvironment?.strategies.length >= 30;
|
||||
|
||||
const { limitMessage, limitReached } = useStrategyLimit(
|
||||
featureEnvironment?.strategies.length || 0,
|
||||
);
|
||||
|
||||
return (
|
||||
<ConditionallyRender
|
||||
@ -187,7 +200,7 @@ const FeatureOverviewEnvironment = ({
|
||||
size='small'
|
||||
disableReason={
|
||||
limitReached
|
||||
? 'Limit reached'
|
||||
? limitMessage
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
@ -234,7 +247,7 @@ const FeatureOverviewEnvironment = ({
|
||||
environmentId={env.name}
|
||||
disableReason={
|
||||
limitReached
|
||||
? 'Limit reached'
|
||||
? limitMessage
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
|
@ -28,4 +28,15 @@ export const defaultValue: IUiConfig = {
|
||||
},
|
||||
],
|
||||
networkViewEnabled: false,
|
||||
resourceLimits: {
|
||||
segmentValues: 1000,
|
||||
strategySegments: 5,
|
||||
signalEndpoints: 5,
|
||||
actionSetActions: 10,
|
||||
actionSetsPerProject: 5,
|
||||
actionSetFilters: 5,
|
||||
actionSetFilterValues: 25,
|
||||
signalTokensPerEndpoint: 5,
|
||||
featureEnvironmentStrategies: 30,
|
||||
},
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import type { Variant } from 'utils/variants';
|
||||
import type { ResourceLimitsSchema } from '../openapi';
|
||||
|
||||
export interface IUiConfig {
|
||||
authenticationType?: string;
|
||||
@ -28,6 +29,7 @@ export interface IUiConfig {
|
||||
segmentValuesLimit?: number;
|
||||
strategySegmentsLimit?: number;
|
||||
frontendApiOrigins?: string[];
|
||||
resourceLimits: ResourceLimitsSchema;
|
||||
}
|
||||
|
||||
export interface IProclamationToast {
|
||||
|
@ -24,4 +24,6 @@ export interface ResourceLimitsSchema {
|
||||
signalTokensPerEndpoint: number;
|
||||
/** The maximum number of strategy segments allowed. */
|
||||
strategySegments: number;
|
||||
/** The maximum number of feature environment strategies allowed. */
|
||||
featureEnvironmentStrategies: number;
|
||||
}
|
||||
|
@ -196,6 +196,7 @@ exports[`should create default config 1`] = `
|
||||
"actionSetFilterValues": 25,
|
||||
"actionSetFilters": 5,
|
||||
"actionSetsPerProject": 5,
|
||||
"featureEnvironmentStrategies": 30,
|
||||
"segmentValues": 1000,
|
||||
"signalEndpoints": 5,
|
||||
"signalTokensPerEndpoint": 5,
|
||||
|
@ -649,6 +649,10 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig {
|
||||
process.env.UNLEASH_SIGNAL_TOKENS_PER_ENDPOINT_LIMIT,
|
||||
5,
|
||||
),
|
||||
featureEnvironmentStrategies: parseEnvVarNumber(
|
||||
process.env.UNLEASH_FEATURE_ENVIRONMENT_STRATEGIES_LIMIT,
|
||||
30,
|
||||
),
|
||||
};
|
||||
|
||||
return {
|
||||
|
@ -60,7 +60,7 @@ export const createFeatureToggleService = (
|
||||
db: Db,
|
||||
config: IUnleashConfig,
|
||||
): FeatureToggleService => {
|
||||
const { getLogger, eventBus, flagResolver } = config;
|
||||
const { getLogger, eventBus, flagResolver, resourceLimits } = config;
|
||||
const featureStrategiesStore = new FeatureStrategiesStore(
|
||||
db,
|
||||
eventBus,
|
||||
@ -142,7 +142,7 @@ export const createFeatureToggleService = (
|
||||
contextFieldStore,
|
||||
strategyStore,
|
||||
},
|
||||
{ getLogger, flagResolver, eventBus },
|
||||
{ getLogger, flagResolver, eventBus, resourceLimits },
|
||||
segmentService,
|
||||
accessService,
|
||||
eventService,
|
||||
@ -156,7 +156,7 @@ export const createFeatureToggleService = (
|
||||
};
|
||||
|
||||
export const createFakeFeatureToggleService = (config: IUnleashConfig) => {
|
||||
const { getLogger, flagResolver } = config;
|
||||
const { getLogger, flagResolver, resourceLimits } = config;
|
||||
const eventStore = new FakeEventStore();
|
||||
const strategyStore = new FakeStrategiesStore();
|
||||
const featureStrategiesStore = new FakeFeatureStrategiesStore();
|
||||
@ -204,7 +204,12 @@ export const createFakeFeatureToggleService = (config: IUnleashConfig) => {
|
||||
contextFieldStore,
|
||||
strategyStore,
|
||||
},
|
||||
{ getLogger, flagResolver, eventBus: new EventEmitter() },
|
||||
{
|
||||
getLogger,
|
||||
flagResolver,
|
||||
eventBus: new EventEmitter(),
|
||||
resourceLimits,
|
||||
},
|
||||
segmentService,
|
||||
accessService,
|
||||
eventService,
|
||||
|
@ -108,6 +108,7 @@ import { FEATURES_CREATED_BY_PROCESSED } from '../../metric-events';
|
||||
import { allSettledWithRejection } from '../../util/allSettledWithRejection';
|
||||
import type EventEmitter from 'node:events';
|
||||
import type { IFeatureLifecycleReadModel } from '../feature-lifecycle/feature-lifecycle-read-model-type';
|
||||
import type { ResourceLimitsSchema } from '../../openapi';
|
||||
|
||||
interface IFeatureContext {
|
||||
featureName: string;
|
||||
@ -179,6 +180,8 @@ class FeatureToggleService {
|
||||
|
||||
private eventBus: EventEmitter;
|
||||
|
||||
private resourceLimits: ResourceLimitsSchema;
|
||||
|
||||
constructor(
|
||||
{
|
||||
featureStrategiesStore,
|
||||
@ -204,7 +207,11 @@ class FeatureToggleService {
|
||||
getLogger,
|
||||
flagResolver,
|
||||
eventBus,
|
||||
}: Pick<IUnleashConfig, 'getLogger' | 'flagResolver' | 'eventBus'>,
|
||||
resourceLimits,
|
||||
}: Pick<
|
||||
IUnleashConfig,
|
||||
'getLogger' | 'flagResolver' | 'eventBus' | 'resourceLimits'
|
||||
>,
|
||||
segmentService: ISegmentService,
|
||||
accessService: AccessService,
|
||||
eventService: EventService,
|
||||
@ -233,6 +240,7 @@ class FeatureToggleService {
|
||||
this.dependentFeaturesService = dependentFeaturesService;
|
||||
this.featureLifecycleReadModel = featureLifecycleReadModel;
|
||||
this.eventBus = eventBus;
|
||||
this.resourceLimits = resourceLimits;
|
||||
}
|
||||
|
||||
async validateFeaturesContext(
|
||||
@ -647,7 +655,7 @@ class FeatureToggleService {
|
||||
|
||||
await this.validateStrategyLimit(
|
||||
{ featureName, projectId, environment },
|
||||
30,
|
||||
this.resourceLimits.featureEnvironmentStrategies,
|
||||
);
|
||||
|
||||
try {
|
||||
|
@ -14,10 +14,14 @@ const alwaysOnFlagResolver = {
|
||||
} as unknown as IFlagResolver;
|
||||
|
||||
test('Should not allow to exceed strategy limit', async () => {
|
||||
const LIMIT = 3;
|
||||
const { featureToggleService, featureToggleStore } =
|
||||
createFakeFeatureToggleService({
|
||||
getLogger,
|
||||
flagResolver: alwaysOnFlagResolver,
|
||||
resourceLimits: {
|
||||
featureEnvironmentStrategies: LIMIT,
|
||||
},
|
||||
} as unknown as IUnleashConfig);
|
||||
|
||||
const addStrategy = () =>
|
||||
@ -31,11 +35,11 @@ test('Should not allow to exceed strategy limit', async () => {
|
||||
createdByUserId: 1,
|
||||
});
|
||||
|
||||
for (let i = 0; i < 30; i++) {
|
||||
for (let i = 0; i < LIMIT; i++) {
|
||||
await addStrategy();
|
||||
}
|
||||
|
||||
await expect(addStrategy()).rejects.toThrow(
|
||||
'Strategy limit of 30 exceeded',
|
||||
`Strategy limit of ${LIMIT} exceeded`,
|
||||
);
|
||||
});
|
||||
|
@ -13,6 +13,7 @@ export const resourceLimitsSchema = {
|
||||
'actionSetFilterValues',
|
||||
'signalEndpoints',
|
||||
'signalTokensPerEndpoint',
|
||||
'featureEnvironmentStrategies',
|
||||
],
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
@ -61,6 +62,12 @@ export const resourceLimitsSchema = {
|
||||
description:
|
||||
'The maximum number of signal tokens per endpoint allowed.',
|
||||
},
|
||||
featureEnvironmentStrategies: {
|
||||
type: 'integer',
|
||||
example: 30,
|
||||
description:
|
||||
'The maximum number of feature environment strategies allowed.',
|
||||
},
|
||||
},
|
||||
components: {},
|
||||
} as const;
|
||||
|
Loading…
Reference in New Issue
Block a user