1
0
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:
Mateusz Kwasniewski 2023-09-12 15:36:19 +02:00 committed by GitHub
parent ed6547b6f1
commit 2b2f5e20fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 94 additions and 5 deletions

View File

@ -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[]> {

View File

@ -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: [

View File

@ -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(

View File

@ -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();
}

View File

@ -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;
}