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:
parent
66304cf8e7
commit
7b7a2a706c
@ -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,
|
||||||
);
|
);
|
||||||
|
@ -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: [
|
||||||
{
|
{
|
||||||
|
@ -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,
|
||||||
|
@ -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: [
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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',
|
||||||
|
Loading…
Reference in New Issue
Block a user