mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-31 13:47:02 +02:00
feat: link templates applied on flags (#9976)
This commit is contained in:
parent
0429c1985a
commit
57d11ae6b3
@ -61,6 +61,10 @@ import { FakeFeatureCollaboratorsReadModel } from './fake-feature-collaborators-
|
|||||||
import { FeatureCollaboratorsReadModel } from './feature-collaborators-read-model';
|
import { FeatureCollaboratorsReadModel } from './feature-collaborators-read-model';
|
||||||
import { FeatureLinksReadModel } from '../feature-links/feature-links-read-model';
|
import { FeatureLinksReadModel } from '../feature-links/feature-links-read-model';
|
||||||
import { FakeFeatureLinksReadModel } from '../feature-links/fake-feature-links-read-model';
|
import { FakeFeatureLinksReadModel } from '../feature-links/fake-feature-links-read-model';
|
||||||
|
import {
|
||||||
|
createFakeFeatureLinkService,
|
||||||
|
createFeatureLinkService,
|
||||||
|
} from '../feature-links/createFeatureLinkService';
|
||||||
|
|
||||||
export const createFeatureToggleService = (
|
export const createFeatureToggleService = (
|
||||||
db: Db,
|
db: Db,
|
||||||
@ -132,6 +136,8 @@ export const createFeatureToggleService = (
|
|||||||
|
|
||||||
const featureLinksReadModel = new FeatureLinksReadModel(db, eventBus);
|
const featureLinksReadModel = new FeatureLinksReadModel(db, eventBus);
|
||||||
|
|
||||||
|
const featureLinkService = createFeatureLinkService(config)(db);
|
||||||
|
|
||||||
const featureToggleService = new FeatureToggleService(
|
const featureToggleService = new FeatureToggleService(
|
||||||
{
|
{
|
||||||
featureStrategiesStore,
|
featureStrategiesStore,
|
||||||
@ -155,6 +161,7 @@ export const createFeatureToggleService = (
|
|||||||
featureLifecycleReadModel,
|
featureLifecycleReadModel,
|
||||||
featureCollaboratorsReadModel,
|
featureCollaboratorsReadModel,
|
||||||
featureLinksReadModel,
|
featureLinksReadModel,
|
||||||
|
featureLinkService,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return featureToggleService;
|
return featureToggleService;
|
||||||
@ -197,6 +204,7 @@ export const createFakeFeatureToggleService = (config: IUnleashConfig) => {
|
|||||||
const featureCollaboratorsReadModel =
|
const featureCollaboratorsReadModel =
|
||||||
new FakeFeatureCollaboratorsReadModel();
|
new FakeFeatureCollaboratorsReadModel();
|
||||||
const featureLinksReadModel = new FakeFeatureLinksReadModel();
|
const featureLinksReadModel = new FakeFeatureLinksReadModel();
|
||||||
|
const { featureLinkService } = createFakeFeatureLinkService(config);
|
||||||
|
|
||||||
const featureToggleService = new FeatureToggleService(
|
const featureToggleService = new FeatureToggleService(
|
||||||
{
|
{
|
||||||
@ -226,6 +234,7 @@ export const createFakeFeatureToggleService = (config: IUnleashConfig) => {
|
|||||||
featureLifecycleReadModel,
|
featureLifecycleReadModel,
|
||||||
featureCollaboratorsReadModel,
|
featureCollaboratorsReadModel,
|
||||||
featureLinksReadModel,
|
featureLinksReadModel,
|
||||||
|
featureLinkService,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
|
@ -117,6 +117,7 @@ import type {
|
|||||||
IFeatureLink,
|
IFeatureLink,
|
||||||
IFeatureLinksReadModel,
|
IFeatureLinksReadModel,
|
||||||
} from '../feature-links/feature-links-read-model-type';
|
} from '../feature-links/feature-links-read-model-type';
|
||||||
|
import type FeatureLinkService from '../feature-links/feature-link-service';
|
||||||
|
|
||||||
interface IFeatureContext {
|
interface IFeatureContext {
|
||||||
featureName: string;
|
featureName: string;
|
||||||
@ -176,6 +177,7 @@ export type ServicesAndReadModels = {
|
|||||||
dependentFeaturesService: DependentFeaturesService;
|
dependentFeaturesService: DependentFeaturesService;
|
||||||
featureLifecycleReadModel: IFeatureLifecycleReadModel;
|
featureLifecycleReadModel: IFeatureLifecycleReadModel;
|
||||||
featureCollaboratorsReadModel: IFeatureCollaboratorsReadModel;
|
featureCollaboratorsReadModel: IFeatureCollaboratorsReadModel;
|
||||||
|
featureLinkService: FeatureLinkService;
|
||||||
featureLinksReadModel: IFeatureLinksReadModel;
|
featureLinksReadModel: IFeatureLinksReadModel;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -218,6 +220,8 @@ class FeatureToggleService {
|
|||||||
|
|
||||||
private featureLinksReadModel: IFeatureLinksReadModel;
|
private featureLinksReadModel: IFeatureLinksReadModel;
|
||||||
|
|
||||||
|
private featureLinkService: FeatureLinkService;
|
||||||
|
|
||||||
private dependentFeaturesService: DependentFeaturesService;
|
private dependentFeaturesService: DependentFeaturesService;
|
||||||
|
|
||||||
private eventBus: EventEmitter;
|
private eventBus: EventEmitter;
|
||||||
@ -247,6 +251,7 @@ class FeatureToggleService {
|
|||||||
featureLifecycleReadModel,
|
featureLifecycleReadModel,
|
||||||
featureCollaboratorsReadModel,
|
featureCollaboratorsReadModel,
|
||||||
featureLinksReadModel,
|
featureLinksReadModel,
|
||||||
|
featureLinkService,
|
||||||
}: ServicesAndReadModels,
|
}: ServicesAndReadModels,
|
||||||
) {
|
) {
|
||||||
this.logger = getLogger('services/feature-toggle-service.ts');
|
this.logger = getLogger('services/feature-toggle-service.ts');
|
||||||
@ -269,6 +274,7 @@ class FeatureToggleService {
|
|||||||
this.featureLifecycleReadModel = featureLifecycleReadModel;
|
this.featureLifecycleReadModel = featureLifecycleReadModel;
|
||||||
this.featureCollaboratorsReadModel = featureCollaboratorsReadModel;
|
this.featureCollaboratorsReadModel = featureCollaboratorsReadModel;
|
||||||
this.featureLinksReadModel = featureLinksReadModel;
|
this.featureLinksReadModel = featureLinksReadModel;
|
||||||
|
this.featureLinkService = featureLinkService;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.resourceLimits = resourceLimits;
|
this.resourceLimits = resourceLimits;
|
||||||
}
|
}
|
||||||
@ -1340,6 +1346,8 @@ class FeatureToggleService {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await this.addLinksFromTemplates(projectId, featureName, auditUser);
|
||||||
|
|
||||||
return createdToggle;
|
return createdToggle;
|
||||||
}
|
}
|
||||||
throw new NotFoundError(
|
throw new NotFoundError(
|
||||||
@ -2543,6 +2551,32 @@ class FeatureToggleService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addLinksFromTemplates(
|
||||||
|
projectId: string,
|
||||||
|
featureName: string,
|
||||||
|
auditUser: IAuditUser,
|
||||||
|
) {
|
||||||
|
if (!this.flagResolver.isEnabled('projectLinkTemplates')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const featureLinksFromTemplates = (
|
||||||
|
await this.projectStore.getProjectLinkTemplates(projectId)
|
||||||
|
).map((template) => ({
|
||||||
|
title: template.title,
|
||||||
|
url: template.urlTemplate
|
||||||
|
.replace(/{{project}}/g, projectId)
|
||||||
|
.replace(/{{feature}}/g, featureName),
|
||||||
|
featureName,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
featureLinksFromTemplates.map((link) =>
|
||||||
|
this.featureLinkService.createLink(projectId, link, auditUser),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FeatureToggleService;
|
export default FeatureToggleService;
|
||||||
|
@ -25,16 +25,19 @@ import {
|
|||||||
import type { ISegmentService } from '../../segment/segment-service-interface';
|
import type { ISegmentService } from '../../segment/segment-service-interface';
|
||||||
import {
|
import {
|
||||||
createEventsService,
|
createEventsService,
|
||||||
|
createFeatureLinkService,
|
||||||
createFeatureToggleService,
|
createFeatureToggleService,
|
||||||
createSegmentService,
|
createSegmentService,
|
||||||
} from '../..';
|
} from '../..';
|
||||||
import { insertLastSeenAt } from '../../../../test/e2e/helpers/test-helper';
|
import { insertLastSeenAt } from '../../../../test/e2e/helpers/test-helper';
|
||||||
import type { EventService } from '../../../services';
|
import type { EventService } from '../../../services';
|
||||||
|
import type FeatureLinkService from '../../feature-links/feature-link-service';
|
||||||
|
|
||||||
let stores: IUnleashStores;
|
let stores: IUnleashStores;
|
||||||
let db: ITestDb;
|
let db: ITestDb;
|
||||||
let service: FeatureToggleService;
|
let service: FeatureToggleService;
|
||||||
let segmentService: ISegmentService;
|
let segmentService: ISegmentService;
|
||||||
|
let featureLinkService: FeatureLinkService;
|
||||||
let eventService: EventService;
|
let eventService: EventService;
|
||||||
let environmentService: EnvironmentService;
|
let environmentService: EnvironmentService;
|
||||||
let unleashConfig: IUnleashConfig;
|
let unleashConfig: IUnleashConfig;
|
||||||
@ -49,19 +52,27 @@ const mockConstraints = (): IConstraint[] => {
|
|||||||
const irrelevantDate = new Date();
|
const irrelevantDate = new Date();
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const config = createTestConfig({
|
const flags = {
|
||||||
experimental: { flags: {} },
|
featureLinks: true,
|
||||||
});
|
projectLinkTemplates: true,
|
||||||
|
};
|
||||||
|
const config = createTestConfig({ experimental: { flags } });
|
||||||
|
|
||||||
db = await dbInit(
|
db = await dbInit(
|
||||||
'feature_toggle_service_v2_service_serial',
|
'feature_toggle_service_v2_service_serial',
|
||||||
config.getLogger,
|
config.getLogger,
|
||||||
{ dbInitMethod: 'legacy' as const },
|
{
|
||||||
|
dbInitMethod: 'legacy' as const,
|
||||||
|
experimental: { flags },
|
||||||
|
},
|
||||||
);
|
);
|
||||||
unleashConfig = config;
|
unleashConfig = config;
|
||||||
stores = db.stores;
|
stores = db.stores;
|
||||||
|
|
||||||
segmentService = createSegmentService(db.rawDatabase, config);
|
segmentService = createSegmentService(db.rawDatabase, config);
|
||||||
|
|
||||||
|
featureLinkService = createFeatureLinkService(config)(db.rawDatabase);
|
||||||
|
|
||||||
service = createFeatureToggleService(db.rawDatabase, config);
|
service = createFeatureToggleService(db.rawDatabase, config);
|
||||||
|
|
||||||
eventService = createEventsService(db.rawDatabase, config);
|
eventService = createEventsService(db.rawDatabase, config);
|
||||||
@ -878,3 +889,47 @@ test('Should enable disabled strategies on feature environment enabled', async (
|
|||||||
const strategy = await service.getStrategy(createdConfig.id);
|
const strategy = await service.getStrategy(createdConfig.id);
|
||||||
expect(strategy).toMatchObject({ ...config, disabled: false });
|
expect(strategy).toMatchObject({ ...config, disabled: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Should add links from templates when creating a feature flag', async () => {
|
||||||
|
const projectId = 'default';
|
||||||
|
const featureName = 'test-link-feature';
|
||||||
|
|
||||||
|
await stores.projectStore.updateProjectEnterpriseSettings({
|
||||||
|
id: projectId,
|
||||||
|
linkTemplates: [
|
||||||
|
{
|
||||||
|
title: 'Issue tracker',
|
||||||
|
urlTemplate:
|
||||||
|
'https://issues.example.com/project/{{project}}/tasks/{{feature}}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Docs',
|
||||||
|
urlTemplate: 'https://docs.example.com/{{project}}/{{feature}}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await service.createFeatureToggle(
|
||||||
|
projectId,
|
||||||
|
{ name: featureName },
|
||||||
|
TEST_AUDIT_USER,
|
||||||
|
);
|
||||||
|
|
||||||
|
const links = await featureLinkService.getAll();
|
||||||
|
|
||||||
|
expect(links.length).toBe(2);
|
||||||
|
expect(links).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
title: 'Issue tracker',
|
||||||
|
url: `https://issues.example.com/project/${projectId}/tasks/${featureName}`,
|
||||||
|
featureName,
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
title: 'Docs',
|
||||||
|
url: `https://docs.example.com/${projectId}/${featureName}`,
|
||||||
|
featureName,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
@ -69,6 +69,7 @@ import {
|
|||||||
createFakeAccessService,
|
createFakeAccessService,
|
||||||
createFakeEnvironmentService,
|
createFakeEnvironmentService,
|
||||||
createFakeEventsService,
|
createFakeEventsService,
|
||||||
|
createFakeFeatureLinkService,
|
||||||
createFakeFeatureToggleService,
|
createFakeFeatureToggleService,
|
||||||
createFakeProjectService,
|
createFakeProjectService,
|
||||||
createFakeUserSubscriptionsService,
|
createFakeUserSubscriptionsService,
|
||||||
@ -158,7 +159,6 @@ import {
|
|||||||
createFakeContextService,
|
createFakeContextService,
|
||||||
} from '../features/context/createContextService';
|
} from '../features/context/createContextService';
|
||||||
import { UniqueConnectionService } from '../features/unique-connection/unique-connection-service';
|
import { UniqueConnectionService } from '../features/unique-connection/unique-connection-service';
|
||||||
import { createFakeFeatureLinkService } from '../features/feature-links/createFeatureLinkService';
|
|
||||||
import { UnknownFlagsService } from '../features/metrics/unknown-flags/unknown-flags-service';
|
import { UnknownFlagsService } from '../features/metrics/unknown-flags/unknown-flags-service';
|
||||||
|
|
||||||
export const createServices = (
|
export const createServices = (
|
||||||
@ -323,6 +323,15 @@ export const createServices = (
|
|||||||
const importService = db
|
const importService = db
|
||||||
? withTransactional(deferredExportImportTogglesService(config), db)
|
? withTransactional(deferredExportImportTogglesService(config), db)
|
||||||
: withFakeTransactional(createFakeExportImportTogglesService(config));
|
: withFakeTransactional(createFakeExportImportTogglesService(config));
|
||||||
|
|
||||||
|
const transactionalFeatureLinkService = db
|
||||||
|
? withTransactional(createFeatureLinkService(config), db)
|
||||||
|
: withFakeTransactional(
|
||||||
|
createFakeFeatureLinkService(config).featureLinkService,
|
||||||
|
);
|
||||||
|
|
||||||
|
const featureLinkService = transactionalFeatureLinkService;
|
||||||
|
|
||||||
const featureToggleService = db
|
const featureToggleService = db
|
||||||
? withTransactional((db) => createFeatureToggleService(db, config), db)
|
? withTransactional((db) => createFeatureToggleService(db, config), db)
|
||||||
: withFakeTransactional(
|
: withFakeTransactional(
|
||||||
@ -417,14 +426,6 @@ export const createServices = (
|
|||||||
? withTransactional(createUserSubscriptionsService(config), db)
|
? withTransactional(createUserSubscriptionsService(config), db)
|
||||||
: withFakeTransactional(createFakeUserSubscriptionsService(config));
|
: withFakeTransactional(createFakeUserSubscriptionsService(config));
|
||||||
|
|
||||||
const transactionalFeatureLinkService = db
|
|
||||||
? withTransactional(createFeatureLinkService(config), db)
|
|
||||||
: withFakeTransactional(
|
|
||||||
createFakeFeatureLinkService(config).featureLinkService,
|
|
||||||
);
|
|
||||||
|
|
||||||
const featureLinkService = transactionalFeatureLinkService;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
transactionalAccessService,
|
transactionalAccessService,
|
||||||
accessService,
|
accessService,
|
||||||
|
Loading…
Reference in New Issue
Block a user