1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00

feat: link templates applied on flags (#9976)

This commit is contained in:
Tymoteusz Czech 2025-05-13 13:16:26 +02:00 committed by GitHub
parent 0429c1985a
commit 57d11ae6b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 112 additions and 13 deletions

View File

@ -61,6 +61,10 @@ import { FakeFeatureCollaboratorsReadModel } from './fake-feature-collaborators-
import { FeatureCollaboratorsReadModel } from './feature-collaborators-read-model';
import { FeatureLinksReadModel } from '../feature-links/feature-links-read-model';
import { FakeFeatureLinksReadModel } from '../feature-links/fake-feature-links-read-model';
import {
createFakeFeatureLinkService,
createFeatureLinkService,
} from '../feature-links/createFeatureLinkService';
export const createFeatureToggleService = (
db: Db,
@ -132,6 +136,8 @@ export const createFeatureToggleService = (
const featureLinksReadModel = new FeatureLinksReadModel(db, eventBus);
const featureLinkService = createFeatureLinkService(config)(db);
const featureToggleService = new FeatureToggleService(
{
featureStrategiesStore,
@ -155,6 +161,7 @@ export const createFeatureToggleService = (
featureLifecycleReadModel,
featureCollaboratorsReadModel,
featureLinksReadModel,
featureLinkService,
},
);
return featureToggleService;
@ -197,6 +204,7 @@ export const createFakeFeatureToggleService = (config: IUnleashConfig) => {
const featureCollaboratorsReadModel =
new FakeFeatureCollaboratorsReadModel();
const featureLinksReadModel = new FakeFeatureLinksReadModel();
const { featureLinkService } = createFakeFeatureLinkService(config);
const featureToggleService = new FeatureToggleService(
{
@ -226,6 +234,7 @@ export const createFakeFeatureToggleService = (config: IUnleashConfig) => {
featureLifecycleReadModel,
featureCollaboratorsReadModel,
featureLinksReadModel,
featureLinkService,
},
);
return {

View File

@ -117,6 +117,7 @@ import type {
IFeatureLink,
IFeatureLinksReadModel,
} from '../feature-links/feature-links-read-model-type';
import type FeatureLinkService from '../feature-links/feature-link-service';
interface IFeatureContext {
featureName: string;
@ -176,6 +177,7 @@ export type ServicesAndReadModels = {
dependentFeaturesService: DependentFeaturesService;
featureLifecycleReadModel: IFeatureLifecycleReadModel;
featureCollaboratorsReadModel: IFeatureCollaboratorsReadModel;
featureLinkService: FeatureLinkService;
featureLinksReadModel: IFeatureLinksReadModel;
};
@ -218,6 +220,8 @@ class FeatureToggleService {
private featureLinksReadModel: IFeatureLinksReadModel;
private featureLinkService: FeatureLinkService;
private dependentFeaturesService: DependentFeaturesService;
private eventBus: EventEmitter;
@ -247,6 +251,7 @@ class FeatureToggleService {
featureLifecycleReadModel,
featureCollaboratorsReadModel,
featureLinksReadModel,
featureLinkService,
}: ServicesAndReadModels,
) {
this.logger = getLogger('services/feature-toggle-service.ts');
@ -269,6 +274,7 @@ class FeatureToggleService {
this.featureLifecycleReadModel = featureLifecycleReadModel;
this.featureCollaboratorsReadModel = featureCollaboratorsReadModel;
this.featureLinksReadModel = featureLinksReadModel;
this.featureLinkService = featureLinkService;
this.eventBus = eventBus;
this.resourceLimits = resourceLimits;
}
@ -1340,6 +1346,8 @@ class FeatureToggleService {
}),
);
await this.addLinksFromTemplates(projectId, featureName, auditUser);
return createdToggle;
}
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;

View File

@ -25,16 +25,19 @@ import {
import type { ISegmentService } from '../../segment/segment-service-interface';
import {
createEventsService,
createFeatureLinkService,
createFeatureToggleService,
createSegmentService,
} from '../..';
import { insertLastSeenAt } from '../../../../test/e2e/helpers/test-helper';
import type { EventService } from '../../../services';
import type FeatureLinkService from '../../feature-links/feature-link-service';
let stores: IUnleashStores;
let db: ITestDb;
let service: FeatureToggleService;
let segmentService: ISegmentService;
let featureLinkService: FeatureLinkService;
let eventService: EventService;
let environmentService: EnvironmentService;
let unleashConfig: IUnleashConfig;
@ -49,19 +52,27 @@ const mockConstraints = (): IConstraint[] => {
const irrelevantDate = new Date();
beforeAll(async () => {
const config = createTestConfig({
experimental: { flags: {} },
});
const flags = {
featureLinks: true,
projectLinkTemplates: true,
};
const config = createTestConfig({ experimental: { flags } });
db = await dbInit(
'feature_toggle_service_v2_service_serial',
config.getLogger,
{ dbInitMethod: 'legacy' as const },
{
dbInitMethod: 'legacy' as const,
experimental: { flags },
},
);
unleashConfig = config;
stores = db.stores;
segmentService = createSegmentService(db.rawDatabase, config);
featureLinkService = createFeatureLinkService(config)(db.rawDatabase);
service = createFeatureToggleService(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);
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,
}),
]),
);
});

View File

@ -69,6 +69,7 @@ import {
createFakeAccessService,
createFakeEnvironmentService,
createFakeEventsService,
createFakeFeatureLinkService,
createFakeFeatureToggleService,
createFakeProjectService,
createFakeUserSubscriptionsService,
@ -158,7 +159,6 @@ import {
createFakeContextService,
} from '../features/context/createContextService';
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';
export const createServices = (
@ -323,6 +323,15 @@ export const createServices = (
const importService = db
? withTransactional(deferredExportImportTogglesService(config), db)
: withFakeTransactional(createFakeExportImportTogglesService(config));
const transactionalFeatureLinkService = db
? withTransactional(createFeatureLinkService(config), db)
: withFakeTransactional(
createFakeFeatureLinkService(config).featureLinkService,
);
const featureLinkService = transactionalFeatureLinkService;
const featureToggleService = db
? withTransactional((db) => createFeatureToggleService(db, config), db)
: withFakeTransactional(
@ -417,14 +426,6 @@ export const createServices = (
? withTransactional(createUserSubscriptionsService(config), db)
: withFakeTransactional(createFakeUserSubscriptionsService(config));
const transactionalFeatureLinkService = db
? withTransactional(createFeatureLinkService(config), db)
: withFakeTransactional(
createFakeFeatureLinkService(config).featureLinkService,
);
const featureLinkService = transactionalFeatureLinkService;
return {
transactionalAccessService,
accessService,