mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
feat: Import feature links (#9958)
This commit is contained in:
parent
d4d6e658ff
commit
d175a5705a
@ -30,6 +30,8 @@ export const ImportExplanation: FC = () => (
|
||||
<li>variants</li>
|
||||
<li>tags</li>
|
||||
<li>feature flag status</li>
|
||||
<li>feature dependencies</li>
|
||||
<li>feature links</li>
|
||||
</ul>
|
||||
</ImportExplanationDescription>
|
||||
<ImportExplanationHeader>Exceptions?</ImportExplanationHeader>
|
||||
|
@ -51,6 +51,10 @@ import {
|
||||
} from '../context/createContextService';
|
||||
import { FakeFeatureLinksReadModel } from '../feature-links/fake-feature-links-read-model';
|
||||
import { FeatureLinksReadModel } from '../feature-links/feature-links-read-model';
|
||||
import {
|
||||
createFakeFeatureLinkService,
|
||||
createFeatureLinkService,
|
||||
} from '../feature-links/createFeatureLinkService';
|
||||
|
||||
export const createFakeExportImportTogglesService = (
|
||||
config: IUnleashConfig,
|
||||
@ -98,6 +102,9 @@ export const createFakeExportImportTogglesService = (
|
||||
|
||||
const featureLinksReadModel = new FakeFeatureLinksReadModel();
|
||||
|
||||
const featureLinkService =
|
||||
createFakeFeatureLinkService(config).featureLinkService;
|
||||
|
||||
return new ExportImportService(
|
||||
{
|
||||
importTogglesStore,
|
||||
@ -118,6 +125,7 @@ export const createFakeExportImportTogglesService = (
|
||||
strategyService,
|
||||
tagTypeService,
|
||||
dependentFeaturesService,
|
||||
featureLinkService,
|
||||
},
|
||||
dependentFeaturesReadModel,
|
||||
segmentReadModel,
|
||||
@ -191,6 +199,8 @@ export const deferredExportImportTogglesService = (
|
||||
|
||||
const featureLinksReadModel = new FeatureLinksReadModel(db, eventBus);
|
||||
|
||||
const featureLinkService = createFeatureLinkService(config)(db);
|
||||
|
||||
return new ExportImportService(
|
||||
{
|
||||
importTogglesStore,
|
||||
@ -211,6 +221,7 @@ export const deferredExportImportTogglesService = (
|
||||
strategyService,
|
||||
tagTypeService,
|
||||
dependentFeaturesService,
|
||||
featureLinkService,
|
||||
},
|
||||
dependentFeaturesReadModel,
|
||||
segmentReadModel,
|
||||
|
@ -59,6 +59,7 @@ import groupBy from 'lodash.groupby';
|
||||
import { allSettledWithRejection } from '../../util/allSettledWithRejection';
|
||||
import type { ISegmentReadModel } from '../segment/segment-read-model-type';
|
||||
import { readFile } from '../../util/read-file';
|
||||
import type FeatureLinkService from '../feature-links/feature-link-service';
|
||||
|
||||
export type IImportService = {
|
||||
validate(
|
||||
@ -131,6 +132,8 @@ export default class ExportImportService
|
||||
|
||||
private featureLinksReadModel: IFeatureLinksReadModel;
|
||||
|
||||
private featureLinkService: FeatureLinkService;
|
||||
|
||||
constructor(
|
||||
stores: Pick<
|
||||
IUnleashStores,
|
||||
@ -155,6 +158,7 @@ export default class ExportImportService
|
||||
tagTypeService,
|
||||
featureTagService,
|
||||
dependentFeaturesService,
|
||||
featureLinkService,
|
||||
}: Pick<
|
||||
IUnleashServices,
|
||||
| 'featureToggleService'
|
||||
@ -165,6 +169,7 @@ export default class ExportImportService
|
||||
| 'tagTypeService'
|
||||
| 'featureTagService'
|
||||
| 'dependentFeaturesService'
|
||||
| 'featureLinkService'
|
||||
>,
|
||||
dependentFeaturesReadModel: IDependentFeaturesReadModel,
|
||||
segmentReadModel: ISegmentReadModel,
|
||||
@ -186,6 +191,7 @@ export default class ExportImportService
|
||||
this.tagTypeService = tagTypeService;
|
||||
this.featureTagService = featureTagService;
|
||||
this.dependentFeaturesService = dependentFeaturesService;
|
||||
this.featureLinkService = featureLinkService;
|
||||
this.importPermissionsService = new ImportPermissionsService(
|
||||
this.importTogglesStore,
|
||||
this.accessService,
|
||||
@ -297,6 +303,9 @@ export default class ExportImportService
|
||||
await this.importTagTypes(dto, auditUser);
|
||||
await this.importTags(dto, auditUser);
|
||||
await this.importContextFields(dto, auditUser);
|
||||
if (this.flagResolver.isEnabled('featureLinks')) {
|
||||
await this.importLinks(dto, auditUser);
|
||||
}
|
||||
}
|
||||
|
||||
async import(
|
||||
@ -355,6 +364,27 @@ export default class ExportImportService
|
||||
await this.importDependencies(dto, user, auditUser);
|
||||
}
|
||||
|
||||
private async importLinks(dto: ImportTogglesSchema, auditUser: IAuditUser) {
|
||||
await this.importTogglesStore.deleteLinksForFeatures(
|
||||
(dto.data.links ?? []).map((featureLink) => featureLink.feature),
|
||||
);
|
||||
|
||||
const links = dto.data.links || [];
|
||||
for (const featureLink of links) {
|
||||
for (const link of featureLink.links) {
|
||||
await this.featureLinkService.createLink(
|
||||
dto.project,
|
||||
{
|
||||
featureName: featureLink.feature,
|
||||
url: link.url,
|
||||
title: link.title || undefined,
|
||||
},
|
||||
auditUser,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async importDependencies(
|
||||
dto: ImportTogglesSchema,
|
||||
user: IUser,
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
type IContextFieldStore,
|
||||
type IEnvironmentStore,
|
||||
type IEventStore,
|
||||
type IFeatureLinkStore,
|
||||
type IFeatureToggleStore,
|
||||
type IProjectStore,
|
||||
type ISegment,
|
||||
@ -35,6 +36,7 @@ let contextFieldStore: IContextFieldStore;
|
||||
let projectStore: IProjectStore;
|
||||
let toggleStore: IFeatureToggleStore;
|
||||
let tagStore: ITagStore;
|
||||
let featureLinkStore: IFeatureLinkStore;
|
||||
|
||||
const defaultStrategy: IStrategyConfig = {
|
||||
name: 'default',
|
||||
@ -179,6 +181,7 @@ beforeAll(async () => {
|
||||
contextFieldStore = db.stores.contextFieldStore;
|
||||
toggleStore = db.stores.featureToggleStore;
|
||||
tagStore = db.stores.tagStore;
|
||||
featureLinkStore = db.stores.featureLinkStore;
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
@ -187,6 +190,7 @@ beforeEach(async () => {
|
||||
await projectStore.deleteAll();
|
||||
await environmentStore.deleteAll();
|
||||
await tagStore.deleteAll();
|
||||
await featureLinkStore.deleteAll();
|
||||
|
||||
await contextFieldStore.deleteAll();
|
||||
await app.createContextField({ name: 'appName' });
|
||||
@ -835,6 +839,15 @@ test('import features to existing project and environment', async () => {
|
||||
],
|
||||
},
|
||||
],
|
||||
links: [
|
||||
{
|
||||
feature: exportedFeature.name,
|
||||
links: [
|
||||
{ url: 'http://example1.com', title: 'link title 1' },
|
||||
{ url: 'http://example2.com' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
@ -857,6 +870,10 @@ test('import features to existing project and environment', async () => {
|
||||
feature: anotherExportedFeature.name,
|
||||
},
|
||||
],
|
||||
links: [
|
||||
{ title: 'link title 1', url: 'http://example1.com' },
|
||||
{ title: null, url: 'http://example2.com' },
|
||||
],
|
||||
});
|
||||
|
||||
const { body: importedFeatureEnvironment } =
|
||||
|
@ -27,7 +27,8 @@ export interface IImportTogglesStore {
|
||||
project: string,
|
||||
): Promise<ProjectFeaturesLimit>;
|
||||
|
||||
deleteTagsForFeatures(tags: string[]): Promise<void>;
|
||||
deleteTagsForFeatures(features: string[]): Promise<void>;
|
||||
deleteLinksForFeatures(features: string[]): Promise<void>;
|
||||
|
||||
strategiesExistForFeatures(
|
||||
featureNames: string[],
|
||||
|
@ -9,6 +9,7 @@ const T = {
|
||||
features: 'features',
|
||||
featureTag: 'feature_tag',
|
||||
projectSettings: 'project_settings',
|
||||
links: 'feature_link',
|
||||
};
|
||||
export class ImportTogglesStore implements IImportTogglesStore {
|
||||
private db: Db;
|
||||
@ -124,4 +125,8 @@ export class ImportTogglesStore implements IImportTogglesStore {
|
||||
async deleteTagsForFeatures(features: string[]): Promise<void> {
|
||||
return this.db(T.featureTag).whereIn('feature_name', features).del();
|
||||
}
|
||||
|
||||
async deleteLinksForFeatures(features: string[]): Promise<void> {
|
||||
return this.db(T.links).whereIn('feature_name', features).del();
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ import type { Context } from 'unleash-client';
|
||||
import { enrichContextWithIp } from './index';
|
||||
import { corsOriginMiddleware } from '../../middleware';
|
||||
import NotImplementedError from '../../error/not-implemented-error';
|
||||
import NotFoundError from '../../error/notfound-error';
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import { minutesToMilliseconds } from 'date-fns';
|
||||
import metricsHelper from '../../util/metrics-helper';
|
||||
|
@ -16,6 +16,8 @@ import { featureEnvironmentSchema } from './feature-environment-schema';
|
||||
import { strategyVariantSchema } from './strategy-variant-schema';
|
||||
import { featureDependenciesSchema } from './feature-dependencies-schema';
|
||||
import { dependentFeatureSchema } from './dependent-feature-schema';
|
||||
import { featureLinksSchema } from './feature-links-schema';
|
||||
import { featureLinkSchema } from './feature-link-schema';
|
||||
|
||||
export const importTogglesSchema = {
|
||||
$id: '#/components/schemas/importTogglesSchema',
|
||||
@ -60,6 +62,8 @@ export const importTogglesSchema = {
|
||||
tagTypeSchema,
|
||||
featureDependenciesSchema,
|
||||
dependentFeatureSchema,
|
||||
featureLinksSchema,
|
||||
featureLinkSchema,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
@ -444,6 +444,8 @@ export const createServices = (
|
||||
createFakeFeatureLinkService(config).featureLinkService,
|
||||
);
|
||||
|
||||
const featureLinkService = transactionalFeatureLinkService;
|
||||
|
||||
return {
|
||||
transactionalAccessService,
|
||||
accessService,
|
||||
@ -514,6 +516,7 @@ export const createServices = (
|
||||
uniqueConnectionService,
|
||||
featureLifecycleReadModel,
|
||||
transactionalFeatureLinkService,
|
||||
featureLinkService,
|
||||
unknownFlagsService,
|
||||
};
|
||||
};
|
||||
|
@ -136,5 +136,6 @@ export interface IUnleashServices {
|
||||
uniqueConnectionService: UniqueConnectionService;
|
||||
featureLifecycleReadModel: IFeatureLifecycleReadModel;
|
||||
transactionalFeatureLinkService: WithTransactional<FeatureLinkService>;
|
||||
featureLinkService: FeatureLinkService;
|
||||
unknownFlagsService: UnknownFlagsService;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user