Skip to content

Commit

Permalink
feat: Import limit validation (#4669)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwasniew committed Sep 12, 2023
1 parent ed6547b commit 2b2f5e2
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 5 deletions.
18 changes: 17 additions & 1 deletion src/lib/features/export-import-toggles/export-import-service.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -158,6 +161,7 @@ export default class ExportImportService {
missingPermissions,
duplicateFeatures,
featureNameCheckResult,
featureLimitResult,
] = await Promise.all([
this.getUnsupportedStrategies(dto),
this.getUsedCustomStrategies(dto),
Expand All @@ -172,6 +176,7 @@ export default class ExportImportService {
),
this.getDuplicateFeatures(dto),
this.getInvalidFeatureNames(dto),
this.getFeatureLimit(dto),
]);

const errors = ImportValidationMessages.compileErrors({
Expand All @@ -181,6 +186,7 @@ export default class ExportImportService {
otherProjectFeatures,
duplicateFeatures,
featureNameCheckResult,
featureLimitResult,
});
const warnings = ImportValidationMessages.compileWarnings({
archivedFeatures,
Expand Down Expand Up @@ -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[]> {
Expand Down
19 changes: 16 additions & 3 deletions src/lib/features/export-import-toggles/export-import.e2e.test.ts
Expand Up @@ -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',
Expand All @@ -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: [
Expand Down Expand Up @@ -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: [
Expand Down
@@ -1,3 +1,9 @@
export interface ProjectFeaturesLimit {
limit: number;
newFeaturesCount: number;
currentFeaturesCount: number;
}

export interface IImportTogglesStore {
deleteStrategiesForFeatures(
featureNames: string[],
Expand All @@ -16,6 +22,11 @@ export interface IImportTogglesStore {
project: string,
): Promise<string[]>;

getProjectFeaturesLimit(
featureNames: string[],
project: string,
): Promise<ProjectFeaturesLimit>;

deleteTagsForFeatures(tags: string[]): Promise<void>;

strategiesExistForFeatures(
Expand Down
38 changes: 37 additions & 1 deletion src/lib/features/export-import-toggles/import-toggles-store.ts
@@ -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;
Expand Down Expand Up @@ -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();
}
Expand Down
Expand Up @@ -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;
Expand All @@ -12,6 +13,7 @@ export interface IErrorsParams {
otherProjectFeatures: string[];
duplicateFeatures: string[];
featureNameCheckResult: FeatureNameCheckResult;
featureLimitResult: ProjectFeaturesLimit;
}

export interface IWarningParams {
Expand Down Expand Up @@ -43,6 +45,7 @@ export class ImportValidationMessages {
otherProjectFeatures,
duplicateFeatures,
featureNameCheckResult,
featureLimitResult,
}: IErrorsParams): ImportTogglesValidateItemSchema[] {
const errors: ImportTogglesValidateItemSchema[] = [];

Expand Down Expand Up @@ -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;
}
Expand Down

0 comments on commit 2b2f5e2

Please sign in to comment.