mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +01:00
feat: dependencies import validation (#5023)
This commit is contained in:
parent
b6d945befc
commit
2263a1f062
@ -340,7 +340,7 @@ test('validate import data', async () => {
|
|||||||
errors: [
|
errors: [
|
||||||
{
|
{
|
||||||
message:
|
message:
|
||||||
'We detected the following custom strategy in the import file that needs to be created first:',
|
'We detected the following custom strategy that needs to be created first:',
|
||||||
affectedItems: ['customStrategy'],
|
affectedItems: ['customStrategy'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -351,7 +351,7 @@ test('validate import data', async () => {
|
|||||||
{
|
{
|
||||||
affectedItems: ['customSegment'],
|
affectedItems: ['customSegment'],
|
||||||
message:
|
message:
|
||||||
'We detected the following segments in the import file that need to be created first:',
|
'We detected the following segments that need to be created first:',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
warnings: [
|
warnings: [
|
||||||
|
@ -52,6 +52,7 @@ import { FeatureNameCheckResultWithFeaturePattern } from '../feature-toggle/feat
|
|||||||
import { IDependentFeaturesReadModel } from '../dependent-features/dependent-features-read-model-type';
|
import { IDependentFeaturesReadModel } from '../dependent-features/dependent-features-read-model-type';
|
||||||
import groupBy from 'lodash.groupby';
|
import groupBy from 'lodash.groupby';
|
||||||
import { ISegmentService } from '../../segments/segment-service-interface';
|
import { ISegmentService } from '../../segments/segment-service-interface';
|
||||||
|
import { FeatureDependenciesSchema } from '../../openapi/spec/feature-dependencies-schema';
|
||||||
|
|
||||||
export type IImportService = {
|
export type IImportService = {
|
||||||
validate(
|
validate(
|
||||||
@ -194,6 +195,7 @@ export default class ExportImportService
|
|||||||
featureNameCheckResult,
|
featureNameCheckResult,
|
||||||
featureLimitResult,
|
featureLimitResult,
|
||||||
unsupportedSegments,
|
unsupportedSegments,
|
||||||
|
unsupportedDependencies,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
this.getUnsupportedStrategies(dto),
|
this.getUnsupportedStrategies(dto),
|
||||||
this.getUsedCustomStrategies(dto),
|
this.getUsedCustomStrategies(dto),
|
||||||
@ -210,6 +212,7 @@ export default class ExportImportService
|
|||||||
this.getInvalidFeatureNames(dto),
|
this.getInvalidFeatureNames(dto),
|
||||||
this.getFeatureLimit(dto),
|
this.getFeatureLimit(dto),
|
||||||
this.getUnsupportedSegments(dto),
|
this.getUnsupportedSegments(dto),
|
||||||
|
this.getMissingDependencies(dto),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const errors = ImportValidationMessages.compileErrors({
|
const errors = ImportValidationMessages.compileErrors({
|
||||||
@ -221,6 +224,7 @@ export default class ExportImportService
|
|||||||
featureNameCheckResult,
|
featureNameCheckResult,
|
||||||
featureLimitResult,
|
featureLimitResult,
|
||||||
segments: unsupportedSegments,
|
segments: unsupportedSegments,
|
||||||
|
dependencies: unsupportedDependencies,
|
||||||
});
|
});
|
||||||
const warnings = ImportValidationMessages.compileWarnings({
|
const warnings = ImportValidationMessages.compileWarnings({
|
||||||
archivedFeatures,
|
archivedFeatures,
|
||||||
@ -250,6 +254,7 @@ export default class ExportImportService
|
|||||||
this.importPermissionsService.verifyPermissions(dto, user, mode),
|
this.importPermissionsService.verifyPermissions(dto, user, mode),
|
||||||
this.verifyFeatures(dto),
|
this.verifyFeatures(dto),
|
||||||
this.verifySegments(dto),
|
this.verifySegments(dto),
|
||||||
|
this.verifyDependencies(dto),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,6 +464,32 @@ export default class ExportImportService
|
|||||||
: [];
|
: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getMissingDependencies(
|
||||||
|
dto: ImportTogglesSchema,
|
||||||
|
): Promise<string[]> {
|
||||||
|
const dependentFeatures =
|
||||||
|
dto.data.dependencies?.flatMap((dependency) =>
|
||||||
|
dependency.dependencies.map((d) => d.feature),
|
||||||
|
) || [];
|
||||||
|
const importedFeatures = dto.data.features.map((f) => f.name);
|
||||||
|
|
||||||
|
const missingFromImported = dependentFeatures.filter(
|
||||||
|
(feature) => !importedFeatures.includes(feature),
|
||||||
|
);
|
||||||
|
|
||||||
|
let missingFeatures: string[] = [];
|
||||||
|
|
||||||
|
if (missingFromImported.length) {
|
||||||
|
const featuresFromStore = (
|
||||||
|
await this.toggleStore.getAllByNames(missingFromImported)
|
||||||
|
).map((f) => f.name);
|
||||||
|
missingFeatures = missingFromImported.filter(
|
||||||
|
(feature) => !featuresFromStore.includes(feature),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return missingFeatures;
|
||||||
|
}
|
||||||
|
|
||||||
private async verifySegments(dto: ImportTogglesSchema) {
|
private async verifySegments(dto: ImportTogglesSchema) {
|
||||||
const unsupportedSegments = await this.getUnsupportedSegments(dto);
|
const unsupportedSegments = await this.getUnsupportedSegments(dto);
|
||||||
if (unsupportedSegments.length > 0) {
|
if (unsupportedSegments.length > 0) {
|
||||||
@ -468,6 +499,17 @@ export default class ExportImportService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async verifyDependencies(dto: ImportTogglesSchema) {
|
||||||
|
const unsupportedDependencies = await this.getMissingDependencies(dto);
|
||||||
|
if (unsupportedDependencies.length > 0) {
|
||||||
|
throw new BadDataError(
|
||||||
|
`The following dependent features are missing: ${unsupportedDependencies.join(
|
||||||
|
', ',
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async verifyContextFields(dto: ImportTogglesSchema) {
|
private async verifyContextFields(dto: ImportTogglesSchema) {
|
||||||
const unsupportedContextFields = await this.getUnsupportedContextFields(
|
const unsupportedContextFields = await this.getUnsupportedContextFields(
|
||||||
dto,
|
dto,
|
||||||
|
@ -1006,6 +1006,16 @@ test('validate import data', async () => {
|
|||||||
},
|
},
|
||||||
createdContextField,
|
createdContextField,
|
||||||
],
|
],
|
||||||
|
dependencies: [
|
||||||
|
{
|
||||||
|
feature: 'childFeature',
|
||||||
|
dependencies: [
|
||||||
|
{
|
||||||
|
feature: 'parentFeature',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1023,7 +1033,7 @@ test('validate import data', async () => {
|
|||||||
errors: [
|
errors: [
|
||||||
{
|
{
|
||||||
message:
|
message:
|
||||||
'We detected the following custom strategy in the import file that needs to be created first:',
|
'We detected the following custom strategy that needs to be created first:',
|
||||||
affectedItems: ['customStrategy'],
|
affectedItems: ['customStrategy'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1051,9 +1061,14 @@ test('validate import data', async () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
message:
|
message:
|
||||||
'We detected the following segments in the import file that need to be created first:',
|
'We detected the following segments that need to be created first:',
|
||||||
affectedItems: ['customSegment'],
|
affectedItems: ['customSegment'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
affectedItems: ['parentFeature'],
|
||||||
|
message:
|
||||||
|
'We detected the following dependencies that need to be created first:',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
warnings: [
|
warnings: [
|
||||||
{
|
{
|
||||||
|
@ -15,6 +15,7 @@ export interface IErrorsParams {
|
|||||||
featureNameCheckResult: FeatureNameCheckResultWithFeaturePattern;
|
featureNameCheckResult: FeatureNameCheckResultWithFeaturePattern;
|
||||||
featureLimitResult: ProjectFeaturesLimit;
|
featureLimitResult: ProjectFeaturesLimit;
|
||||||
segments: string[];
|
segments: string[];
|
||||||
|
dependencies: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWarningParams {
|
export interface IWarningParams {
|
||||||
@ -48,13 +49,14 @@ export class ImportValidationMessages {
|
|||||||
featureNameCheckResult,
|
featureNameCheckResult,
|
||||||
featureLimitResult,
|
featureLimitResult,
|
||||||
segments,
|
segments,
|
||||||
|
dependencies,
|
||||||
}: IErrorsParams): ImportTogglesValidateItemSchema[] {
|
}: IErrorsParams): ImportTogglesValidateItemSchema[] {
|
||||||
const errors: ImportTogglesValidateItemSchema[] = [];
|
const errors: ImportTogglesValidateItemSchema[] = [];
|
||||||
|
|
||||||
if (strategies.length > 0) {
|
if (strategies.length > 0) {
|
||||||
errors.push({
|
errors.push({
|
||||||
message:
|
message:
|
||||||
'We detected the following custom strategy in the import file that needs to be created first:',
|
'We detected the following custom strategy that needs to be created first:',
|
||||||
affectedItems: strategies.map((strategy) => strategy.name),
|
affectedItems: strategies.map((strategy) => strategy.name),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -111,11 +113,19 @@ export class ImportValidationMessages {
|
|||||||
if (segments.length > 0) {
|
if (segments.length > 0) {
|
||||||
errors.push({
|
errors.push({
|
||||||
message:
|
message:
|
||||||
'We detected the following segments in the import file that need to be created first:',
|
'We detected the following segments that need to be created first:',
|
||||||
affectedItems: segments,
|
affectedItems: segments,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dependencies.length > 0) {
|
||||||
|
errors.push({
|
||||||
|
message:
|
||||||
|
'We detected the following dependencies that need to be created first:',
|
||||||
|
affectedItems: dependencies,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@ import { legalValueSchema } from './legal-value-schema';
|
|||||||
import { tagTypeSchema } from './tag-type-schema';
|
import { tagTypeSchema } from './tag-type-schema';
|
||||||
import { featureEnvironmentSchema } from './feature-environment-schema';
|
import { featureEnvironmentSchema } from './feature-environment-schema';
|
||||||
import { strategyVariantSchema } from './strategy-variant-schema';
|
import { strategyVariantSchema } from './strategy-variant-schema';
|
||||||
|
import { featureDependenciesSchema } from './feature-dependencies-schema';
|
||||||
|
import { dependentFeatureSchema } from './dependent-feature-schema';
|
||||||
|
|
||||||
export const importTogglesSchema = {
|
export const importTogglesSchema = {
|
||||||
$id: '#/components/schemas/importTogglesSchema',
|
$id: '#/components/schemas/importTogglesSchema',
|
||||||
@ -56,6 +58,8 @@ export const importTogglesSchema = {
|
|||||||
parametersSchema,
|
parametersSchema,
|
||||||
legalValueSchema,
|
legalValueSchema,
|
||||||
tagTypeSchema,
|
tagTypeSchema,
|
||||||
|
featureDependenciesSchema,
|
||||||
|
dependentFeatureSchema,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -163,3 +163,4 @@ export * from './update-feature-strategy-segments-schema';
|
|||||||
export * from './dependent-feature-schema';
|
export * from './dependent-feature-schema';
|
||||||
export * from './create-dependent-feature-schema';
|
export * from './create-dependent-feature-schema';
|
||||||
export * from './parent-feature-options-schema';
|
export * from './parent-feature-options-schema';
|
||||||
|
export * from './feature-dependencies-schema';
|
||||||
|
Loading…
Reference in New Issue
Block a user