mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-09 00:18:26 +01:00
feat: Import limit validation (#4669)
This commit is contained in:
parent
ed6547b6f1
commit
2b2f5e20fa
@ -41,7 +41,10 @@ import {
|
||||
TagTypeService,
|
||||
} from '../../services';
|
||||
import { isValidField } from './import-context-validation';
|
||||
import { IImportTogglesStore } from './import-toggles-store-type';
|
||||
import {
|
||||
IImportTogglesStore,
|
||||
ProjectFeaturesLimit,
|
||||
} from './import-toggles-store-type';
|
||||
import { ImportPermissionsService, Mode } from './import-permissions-service';
|
||||
import { ImportValidationMessages } from './import-validation-messages';
|
||||
import { findDuplicates } from '../../util/findDuplicates';
|
||||
@ -158,6 +161,7 @@ export default class ExportImportService {
|
||||
missingPermissions,
|
||||
duplicateFeatures,
|
||||
featureNameCheckResult,
|
||||
featureLimitResult,
|
||||
] = await Promise.all([
|
||||
this.getUnsupportedStrategies(dto),
|
||||
this.getUsedCustomStrategies(dto),
|
||||
@ -172,6 +176,7 @@ export default class ExportImportService {
|
||||
),
|
||||
this.getDuplicateFeatures(dto),
|
||||
this.getInvalidFeatureNames(dto),
|
||||
this.getFeatureLimit(dto),
|
||||
]);
|
||||
|
||||
const errors = ImportValidationMessages.compileErrors({
|
||||
@ -181,6 +186,7 @@ export default class ExportImportService {
|
||||
otherProjectFeatures,
|
||||
duplicateFeatures,
|
||||
featureNameCheckResult,
|
||||
featureLimitResult,
|
||||
});
|
||||
const warnings = ImportValidationMessages.compileWarnings({
|
||||
archivedFeatures,
|
||||
@ -511,6 +517,16 @@ export default class ExportImportService {
|
||||
);
|
||||
}
|
||||
|
||||
private async getFeatureLimit({
|
||||
project,
|
||||
data,
|
||||
}: ImportTogglesSchema): Promise<ProjectFeaturesLimit> {
|
||||
return this.importTogglesStore.getProjectFeaturesLimit(
|
||||
[...new Set(data.features.map((f) => f.name))],
|
||||
project,
|
||||
);
|
||||
}
|
||||
|
||||
private async getUnsupportedStrategies(
|
||||
dto: ImportTogglesSchema,
|
||||
): Promise<FeatureStrategySchema[]> {
|
||||
|
@ -844,7 +844,8 @@ test('reject import with duplicate features', async () => {
|
||||
});
|
||||
|
||||
test('validate import data', async () => {
|
||||
await createProjects();
|
||||
const featureLimit = 1;
|
||||
await createProjects([DEFAULT_PROJECT], featureLimit);
|
||||
|
||||
const contextField: IContextFieldDto = {
|
||||
name: 'validate_context_field',
|
||||
@ -864,7 +865,11 @@ test('validate import data', async () => {
|
||||
...defaultImportPayload,
|
||||
data: {
|
||||
...defaultImportPayload.data,
|
||||
features: [exportedFeature, exportedFeature],
|
||||
features: [
|
||||
exportedFeature,
|
||||
exportedFeature,
|
||||
anotherExportedFeature,
|
||||
],
|
||||
featureStrategies: [{ name: 'customStrategy' }],
|
||||
segments: [{ id: 1, name: 'customSegment' }],
|
||||
contextFields: [
|
||||
@ -909,7 +914,15 @@ test('validate import data', async () => {
|
||||
|
||||
{
|
||||
message: expect.stringMatching(/\btestpattern.+\b/),
|
||||
affectedItems: [defaultFeatureName],
|
||||
affectedItems: [
|
||||
defaultFeatureName,
|
||||
anotherExportedFeature.name,
|
||||
],
|
||||
},
|
||||
{
|
||||
message:
|
||||
'We detected you want to create 2 new features to a project that already has 0 existing features, exceeding the maximum limit of 1.',
|
||||
affectedItems: [],
|
||||
},
|
||||
],
|
||||
warnings: [
|
||||
|
@ -1,3 +1,9 @@
|
||||
export interface ProjectFeaturesLimit {
|
||||
limit: number;
|
||||
newFeaturesCount: number;
|
||||
currentFeaturesCount: number;
|
||||
}
|
||||
|
||||
export interface IImportTogglesStore {
|
||||
deleteStrategiesForFeatures(
|
||||
featureNames: string[],
|
||||
@ -16,6 +22,11 @@ export interface IImportTogglesStore {
|
||||
project: string,
|
||||
): Promise<string[]>;
|
||||
|
||||
getProjectFeaturesLimit(
|
||||
featureNames: string[],
|
||||
project: string,
|
||||
): Promise<ProjectFeaturesLimit>;
|
||||
|
||||
deleteTagsForFeatures(tags: string[]): Promise<void>;
|
||||
|
||||
strategiesExistForFeatures(
|
||||
|
@ -1,10 +1,14 @@
|
||||
import { IImportTogglesStore } from './import-toggles-store-type';
|
||||
import {
|
||||
IImportTogglesStore,
|
||||
ProjectFeaturesLimit,
|
||||
} from './import-toggles-store-type';
|
||||
import { Db } from '../../db/db';
|
||||
|
||||
const T = {
|
||||
featureStrategies: 'feature_strategies',
|
||||
features: 'features',
|
||||
featureTag: 'feature_tag',
|
||||
projectSettings: 'project_settings',
|
||||
};
|
||||
export class ImportTogglesStore implements IImportTogglesStore {
|
||||
private db: Db;
|
||||
@ -86,6 +90,38 @@ export class ImportTogglesStore implements IImportTogglesStore {
|
||||
return rows.map((row) => row.name);
|
||||
}
|
||||
|
||||
async getProjectFeaturesLimit(
|
||||
featureNames: string[],
|
||||
project: string,
|
||||
): Promise<ProjectFeaturesLimit> {
|
||||
const row = await this.db(T.projectSettings)
|
||||
.select(['feature_limit'])
|
||||
.where('project', project)
|
||||
.first();
|
||||
const limit: number = row?.feature_limit ?? Number.MAX_SAFE_INTEGER;
|
||||
|
||||
const existingFeaturesCount = await this.db(T.features)
|
||||
.whereIn('name', featureNames)
|
||||
.andWhere('project', project)
|
||||
.where('archived_at', null)
|
||||
.count()
|
||||
.then((res) => Number(res[0].count));
|
||||
|
||||
const newFeaturesCount = featureNames.length - existingFeaturesCount;
|
||||
|
||||
const currentFeaturesCount = await this.db(T.features)
|
||||
.where('project', project)
|
||||
.count()
|
||||
.where('archived_at', null)
|
||||
.then((res) => Number(res[0].count));
|
||||
|
||||
return {
|
||||
limit,
|
||||
newFeaturesCount,
|
||||
currentFeaturesCount,
|
||||
};
|
||||
}
|
||||
|
||||
async deleteTagsForFeatures(features: string[]): Promise<void> {
|
||||
return this.db(T.featureTag).whereIn('feature_name', features).del();
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
ImportTogglesValidateItemSchema,
|
||||
} from '../../openapi';
|
||||
import { IContextFieldDto } from '../../types/stores/context-field-store';
|
||||
import { ProjectFeaturesLimit } from './import-toggles-store-type';
|
||||
|
||||
export interface IErrorsParams {
|
||||
projectName: string;
|
||||
@ -12,6 +13,7 @@ export interface IErrorsParams {
|
||||
otherProjectFeatures: string[];
|
||||
duplicateFeatures: string[];
|
||||
featureNameCheckResult: FeatureNameCheckResult;
|
||||
featureLimitResult: ProjectFeaturesLimit;
|
||||
}
|
||||
|
||||
export interface IWarningParams {
|
||||
@ -43,6 +45,7 @@ export class ImportValidationMessages {
|
||||
otherProjectFeatures,
|
||||
duplicateFeatures,
|
||||
featureNameCheckResult,
|
||||
featureLimitResult,
|
||||
}: IErrorsParams): ImportTogglesValidateItemSchema[] {
|
||||
const errors: ImportTogglesValidateItemSchema[] = [];
|
||||
|
||||
@ -92,6 +95,16 @@ export class ImportValidationMessages {
|
||||
affectedItems: [...featureNameCheckResult.invalidNames].sort(),
|
||||
});
|
||||
}
|
||||
if (
|
||||
featureLimitResult.currentFeaturesCount +
|
||||
featureLimitResult.newFeaturesCount >
|
||||
featureLimitResult.limit
|
||||
) {
|
||||
errors.push({
|
||||
message: `We detected you want to create ${featureLimitResult.newFeaturesCount} new features to a project that already has ${featureLimitResult.currentFeaturesCount} existing features, exceeding the maximum limit of ${featureLimitResult.limit}.`,
|
||||
affectedItems: [],
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user