1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-19 00:15:43 +01:00

fix: enable segment importing for oss (#5010)

This commit is contained in:
Jaanus Sellin 2023-10-12 13:43:43 +03:00 committed by GitHub
parent 66304cf8e7
commit 7b7a2a706c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 99 additions and 7 deletions

View File

@ -46,6 +46,10 @@ import {
import { DbServiceFactory } from 'lib/db/transaction'; import { DbServiceFactory } from 'lib/db/transaction';
import { DependentFeaturesReadModel } from '../dependent-features/dependent-features-read-model'; import { DependentFeaturesReadModel } from '../dependent-features/dependent-features-read-model';
import { FakeDependentFeaturesReadModel } from '../dependent-features/fake-dependent-features-read-model'; import { FakeDependentFeaturesReadModel } from '../dependent-features/fake-dependent-features-read-model';
import {
createFakeSegmentService,
createSegmentService,
} from '../segment/createSegmentService';
export const createFakeExportImportTogglesService = ( export const createFakeExportImportTogglesService = (
config: IUnleashConfig, config: IUnleashConfig,
@ -106,6 +110,8 @@ export const createFakeExportImportTogglesService = (
); );
const dependentFeaturesReadModel = new FakeDependentFeaturesReadModel(); const dependentFeaturesReadModel = new FakeDependentFeaturesReadModel();
const segmentService = createFakeSegmentService(config);
const exportImportService = new ExportImportService( const exportImportService = new ExportImportService(
{ {
importTogglesStore, importTogglesStore,
@ -126,6 +132,7 @@ export const createFakeExportImportTogglesService = (
contextService, contextService,
strategyService, strategyService,
tagTypeService, tagTypeService,
segmentService,
}, },
dependentFeaturesReadModel, dependentFeaturesReadModel,
); );
@ -220,6 +227,8 @@ export const deferredExportImportTogglesService = (
); );
const dependentFeaturesReadModel = new DependentFeaturesReadModel(db); const dependentFeaturesReadModel = new DependentFeaturesReadModel(db);
const segmentService = createSegmentService(db, config);
const exportImportService = new ExportImportService( const exportImportService = new ExportImportService(
{ {
importTogglesStore, importTogglesStore,
@ -240,6 +249,7 @@ export const deferredExportImportTogglesService = (
contextService, contextService,
strategyService, strategyService,
tagTypeService, tagTypeService,
segmentService,
}, },
dependentFeaturesReadModel, dependentFeaturesReadModel,
); );

View File

@ -170,9 +170,18 @@ const tags = [
]; ];
const tagTypes = [ const tagTypes = [
{ name: 'bestt', description: 'test' }, {
{ name: 'special_tag', description: 'this is my special tag' }, name: 'bestt',
{ name: 'special_tag', description: 'this is my special tag' }, // deliberate duplicate description: 'test',
},
{
name: 'special_tag',
description: 'this is my special tag',
},
{
name: 'special_tag',
description: 'this is my special tag',
}, // deliberate duplicate
]; ];
const importPayload: ImportTogglesSchema = { const importPayload: ImportTogglesSchema = {
@ -199,13 +208,19 @@ const importPayload: ImportTogglesSchema = {
const createUserEditorAccess = async (name, email) => { const createUserEditorAccess = async (name, email) => {
const { userStore } = stores; const { userStore } = stores;
const user = await userStore.insert({ name, email }); const user = await userStore.insert({
name,
email,
});
return user; return user;
}; };
const createUserAdminAccess = async (name, email) => { const createUserAdminAccess = async (name, email) => {
const { userStore } = stores; const { userStore } = stores;
const user = await userStore.insert({ name, email }); const user = await userStore.insert({
name,
email,
});
await accessService.addUserToRole(user.id, adminRole.id, 'default'); await accessService.addUserToRole(user.id, adminRole.id, 'default');
return user; return user;
}; };
@ -301,7 +316,12 @@ test('validate import data', async () => {
data: { data: {
...importPayload.data, ...importPayload.data,
featureStrategies: [{ name: 'customStrategy' }], featureStrategies: [{ name: 'customStrategy' }],
segments: [{ id: 1, name: 'customSegment' }], segments: [
{
id: 1,
name: 'customSegment',
},
],
contextFields: [ contextFields: [
{ {
...contextField, ...contextField,
@ -328,6 +348,11 @@ test('validate import data', async () => {
'We detected the following context fields that do not have matching legal values with the imported ones:', 'We detected the following context fields that do not have matching legal values with the imported ones:',
affectedItems: [contextField.name], affectedItems: [contextField.name],
}, },
{
affectedItems: ['customSegment'],
message:
'We detected the following segments in the import file that need to be created first:',
},
], ],
warnings: [ warnings: [
{ {

View File

@ -51,6 +51,7 @@ import { findDuplicates } from '../../util/findDuplicates';
import { FeatureNameCheckResultWithFeaturePattern } from '../feature-toggle/feature-toggle-service'; import { FeatureNameCheckResultWithFeaturePattern } from '../feature-toggle/feature-toggle-service';
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';
export type IImportService = { export type IImportService = {
validate( validate(
@ -103,6 +104,8 @@ export default class ExportImportService
private tagTypeService: TagTypeService; private tagTypeService: TagTypeService;
private segmentService: ISegmentService;
private featureTagService: FeatureTagService; private featureTagService: FeatureTagService;
private importPermissionsService: ImportPermissionsService; private importPermissionsService: ImportPermissionsService;
@ -133,6 +136,7 @@ export default class ExportImportService
eventService, eventService,
tagTypeService, tagTypeService,
featureTagService, featureTagService,
segmentService,
}: Pick< }: Pick<
IUnleashServices, IUnleashServices,
| 'featureToggleService' | 'featureToggleService'
@ -142,6 +146,7 @@ export default class ExportImportService
| 'eventService' | 'eventService'
| 'tagTypeService' | 'tagTypeService'
| 'featureTagService' | 'featureTagService'
| 'segmentService'
>, >,
dependentFeaturesReadModel: IDependentFeaturesReadModel, dependentFeaturesReadModel: IDependentFeaturesReadModel,
) { ) {
@ -158,6 +163,7 @@ export default class ExportImportService
this.strategyService = strategyService; this.strategyService = strategyService;
this.contextService = contextService; this.contextService = contextService;
this.accessService = accessService; this.accessService = accessService;
this.segmentService = segmentService;
this.eventService = eventService; this.eventService = eventService;
this.tagTypeService = tagTypeService; this.tagTypeService = tagTypeService;
this.featureTagService = featureTagService; this.featureTagService = featureTagService;
@ -187,6 +193,7 @@ export default class ExportImportService
duplicateFeatures, duplicateFeatures,
featureNameCheckResult, featureNameCheckResult,
featureLimitResult, featureLimitResult,
unsupportedSegments,
] = await Promise.all([ ] = await Promise.all([
this.getUnsupportedStrategies(dto), this.getUnsupportedStrategies(dto),
this.getUsedCustomStrategies(dto), this.getUsedCustomStrategies(dto),
@ -202,6 +209,7 @@ export default class ExportImportService
this.getDuplicateFeatures(dto), this.getDuplicateFeatures(dto),
this.getInvalidFeatureNames(dto), this.getInvalidFeatureNames(dto),
this.getFeatureLimit(dto), this.getFeatureLimit(dto),
this.getUnsupportedSegments(dto),
]); ]);
const errors = ImportValidationMessages.compileErrors({ const errors = ImportValidationMessages.compileErrors({
@ -212,6 +220,7 @@ export default class ExportImportService
duplicateFeatures, duplicateFeatures,
featureNameCheckResult, featureNameCheckResult,
featureLimitResult, featureLimitResult,
segments: unsupportedSegments,
}); });
const warnings = ImportValidationMessages.compileWarnings({ const warnings = ImportValidationMessages.compileWarnings({
archivedFeatures, archivedFeatures,
@ -240,6 +249,7 @@ export default class ExportImportService
this.verifyContextFields(dto), this.verifyContextFields(dto),
this.importPermissionsService.verifyPermissions(dto, user, mode), this.importPermissionsService.verifyPermissions(dto, user, mode),
this.verifyFeatures(dto), this.verifyFeatures(dto),
this.verifySegments(dto),
]); ]);
} }
@ -426,6 +436,38 @@ export default class ExportImportService
} }
} }
private async getUnsupportedSegments(
dto: ImportTogglesSchema,
): Promise<string[]> {
const supportedSegments = await this.segmentService.getAll();
const targetProject = dto.project;
return dto.data.segments
? dto.data.segments
.filter(
(importingSegment) =>
!supportedSegments.find(
(existingSegment) =>
importingSegment.name ===
existingSegment.name &&
(!existingSegment.project ||
existingSegment.project ===
targetProject),
),
)
.map((it) => it.name)
: [];
}
private async verifySegments(dto: ImportTogglesSchema) {
const unsupportedSegments = await this.getUnsupportedSegments(dto);
if (unsupportedSegments.length > 0) {
throw new BadDataError(
`Unsupported segments: ${unsupportedSegments.join(', ')}`,
);
}
}
private async verifyContextFields(dto: ImportTogglesSchema) { private async verifyContextFields(dto: ImportTogglesSchema) {
const unsupportedContextFields = await this.getUnsupportedContextFields( const unsupportedContextFields = await this.getUnsupportedContextFields(
dto, dto,

View File

@ -1018,6 +1018,11 @@ test('validate import data', async () => {
'We detected you want to create 2 new features to a project that already has 0 existing features, exceeding the maximum limit of 1.', '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: [], affectedItems: [],
}, },
{
message:
'We detected the following segments in the import file that need to be created first:',
affectedItems: ['customSegment'],
},
], ],
warnings: [ warnings: [
{ {

View File

@ -14,6 +14,7 @@ export interface IErrorsParams {
duplicateFeatures: string[]; duplicateFeatures: string[];
featureNameCheckResult: FeatureNameCheckResultWithFeaturePattern; featureNameCheckResult: FeatureNameCheckResultWithFeaturePattern;
featureLimitResult: ProjectFeaturesLimit; featureLimitResult: ProjectFeaturesLimit;
segments: string[];
} }
export interface IWarningParams { export interface IWarningParams {
@ -46,6 +47,7 @@ export class ImportValidationMessages {
duplicateFeatures, duplicateFeatures,
featureNameCheckResult, featureNameCheckResult,
featureLimitResult, featureLimitResult,
segments,
}: IErrorsParams): ImportTogglesValidateItemSchema[] { }: IErrorsParams): ImportTogglesValidateItemSchema[] {
const errors: ImportTogglesValidateItemSchema[] = []; const errors: ImportTogglesValidateItemSchema[] = [];
@ -106,6 +108,14 @@ export class ImportValidationMessages {
}); });
} }
if (segments.length > 0) {
errors.push({
message:
'We detected the following segments in the import file that need to be created first:',
affectedItems: segments,
});
}
return errors; return errors;
} }

View File

@ -142,7 +142,7 @@ export const exportResultSchema = {
items: { items: {
type: 'object', type: 'object',
additionalProperties: false, additionalProperties: false,
required: ['id'], required: ['id', 'name'],
properties: { properties: {
id: { id: {
type: 'number', type: 'number',