mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-21 13:47:39 +02:00
feat: sql feature link persistence (#9901)
This commit is contained in:
parent
bb82b6920b
commit
c6ab2a1cf7
@ -65,7 +65,7 @@ import { UserUnsubscribeStore } from '../features/user-subscriptions/user-unsubs
|
||||
import { UserSubscriptionsReadModel } from '../features/user-subscriptions/user-subscriptions-read-model';
|
||||
import { UniqueConnectionStore } from '../features/unique-connection/unique-connection-store';
|
||||
import { UniqueConnectionReadModel } from '../features/unique-connection/unique-connection-read-model';
|
||||
import FakeFeatureLinkStore from '../features/feature-links/fake-feature-link-store';
|
||||
import { FeatureLinkStore } from '../features/feature-links/feature-link-store';
|
||||
|
||||
export const createStores = (
|
||||
config: IUnleashConfig,
|
||||
@ -202,7 +202,7 @@ export const createStores = (
|
||||
releasePlanMilestoneStore: new ReleasePlanMilestoneStore(db, config),
|
||||
releasePlanMilestoneStrategyStore:
|
||||
new ReleasePlanMilestoneStrategyStore(db, config),
|
||||
featureLinkStore: new FakeFeatureLinkStore(),
|
||||
featureLinkStore: new FeatureLinkStore(db, config),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,24 @@
|
||||
import type { IUnleashConfig } from '../../types';
|
||||
import FeatureLinkService from './feature-link-service';
|
||||
import FakeFeatureLinkStore from './fake-feature-link-store';
|
||||
import { createFakeEventsService } from '../events/createEventsService';
|
||||
import {
|
||||
createEventsService,
|
||||
createFakeEventsService,
|
||||
} from '../events/createEventsService';
|
||||
import type { Db } from '../../db/db';
|
||||
import { FeatureLinkStore } from './feature-link-store';
|
||||
|
||||
export const createFeatureLinkService =
|
||||
(config: IUnleashConfig) => (db: Db) => {
|
||||
const eventService = createEventsService(db, config);
|
||||
const featureLinkStore = new FeatureLinkStore(db, config);
|
||||
|
||||
return new FeatureLinkService(
|
||||
{ featureLinkStore },
|
||||
config,
|
||||
eventService,
|
||||
);
|
||||
};
|
||||
|
||||
export const createFakeFeatureLinkService = (config: IUnleashConfig) => {
|
||||
const eventService = createFakeEventsService(config);
|
||||
|
@ -7,7 +7,7 @@ import type {
|
||||
export default class FakeFeatureLinkStore implements IFeatureLinkStore {
|
||||
private links: IFeatureLink[] = [];
|
||||
|
||||
async create(link: Omit<IFeatureLink, 'id'>): Promise<IFeatureLink> {
|
||||
async insert(link: Omit<IFeatureLink, 'id'>): Promise<IFeatureLink> {
|
||||
const newLink: IFeatureLink = {
|
||||
...link,
|
||||
id: String(Math.random()),
|
||||
@ -45,9 +45,13 @@ export default class FakeFeatureLinkStore implements IFeatureLinkStore {
|
||||
return this.links;
|
||||
}
|
||||
|
||||
async update(link: IFeatureLink): Promise<IFeatureLink> {
|
||||
await this.delete(link.id);
|
||||
this.links.push(link);
|
||||
return link;
|
||||
async update(
|
||||
id: string,
|
||||
link: Omit<IFeatureLink, 'id'>,
|
||||
): Promise<IFeatureLink> {
|
||||
await this.delete(id);
|
||||
const fullLink = { ...link, id };
|
||||
this.links.push(fullLink);
|
||||
return fullLink;
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ export default class FeatureLinkService {
|
||||
newLink: Omit<IFeatureLink, 'id'>,
|
||||
auditUser: IAuditUser,
|
||||
): Promise<IFeatureLink> {
|
||||
const link = await this.featureLinkStore.create(newLink);
|
||||
const link = await this.featureLinkStore.insert(newLink);
|
||||
|
||||
await this.eventService.storeEvent(
|
||||
new FeatureLinkAddedEvent({
|
||||
@ -66,10 +66,7 @@ export default class FeatureLinkService {
|
||||
throw new NotFoundError(`Could not find link with id ${linkId}`);
|
||||
}
|
||||
|
||||
const link = await this.featureLinkStore.update({
|
||||
...updatedLink,
|
||||
id: linkId,
|
||||
});
|
||||
const link = await this.featureLinkStore.update(linkId, updatedLink);
|
||||
|
||||
await this.eventService.storeEvent(
|
||||
new FeatureLinkUpdatedEvent({
|
||||
|
@ -8,6 +8,6 @@ export interface IFeatureLink {
|
||||
}
|
||||
|
||||
export interface IFeatureLinkStore extends Store<IFeatureLink, string> {
|
||||
create(link: Omit<IFeatureLink, 'id'>): Promise<IFeatureLink>;
|
||||
update(link: IFeatureLink): Promise<IFeatureLink>;
|
||||
insert(link: Omit<IFeatureLink, 'id'>): Promise<IFeatureLink>;
|
||||
update(id: string, link: Omit<IFeatureLink, 'id'>): Promise<IFeatureLink>;
|
||||
}
|
||||
|
33
src/lib/features/feature-links/feature-link-store.ts
Normal file
33
src/lib/features/feature-links/feature-link-store.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import type { Db } from '../../db/db';
|
||||
import type { IFeatureLinkStore } from '../../types';
|
||||
import type { IFeatureLink } from './feature-link-store-type';
|
||||
import { CRUDStore, type CrudStoreConfig } from '../../db/crud/crud-store';
|
||||
import type { Row } from '../../db/crud/row-type';
|
||||
import { ulid } from 'ulidx';
|
||||
|
||||
export class FeatureLinkStore
|
||||
extends CRUDStore<
|
||||
IFeatureLink,
|
||||
Omit<IFeatureLink, 'id'>,
|
||||
Row<IFeatureLink>,
|
||||
Row<Omit<IFeatureLink, 'id'>>,
|
||||
string
|
||||
>
|
||||
implements IFeatureLinkStore
|
||||
{
|
||||
constructor(db: Db, config: CrudStoreConfig) {
|
||||
super('feature_link', db, config);
|
||||
}
|
||||
|
||||
async insert(item: Omit<IFeatureLink, 'id'>): Promise<IFeatureLink> {
|
||||
const id = ulid();
|
||||
const featureLink = {
|
||||
id: ulid(),
|
||||
feature_name: item.featureName,
|
||||
url: item.url,
|
||||
title: item.title,
|
||||
};
|
||||
await this.db('feature_link').insert(featureLink);
|
||||
return { ...item, id };
|
||||
}
|
||||
}
|
@ -58,8 +58,90 @@ const addLink = async (
|
||||
.expect(expectedCode);
|
||||
};
|
||||
|
||||
test('should add feature links', async () => {
|
||||
const updatedLink = async (
|
||||
featureName: string,
|
||||
linkId: string,
|
||||
link: FeatureLinkSchema,
|
||||
expectedCode = 204,
|
||||
) => {
|
||||
return app.request
|
||||
.put(
|
||||
`/api/admin/projects/default/features/${featureName}/link/${linkId}`,
|
||||
)
|
||||
.send(link)
|
||||
.expect(expectedCode);
|
||||
};
|
||||
|
||||
const deleteLink = async (
|
||||
featureName: string,
|
||||
linkId: string,
|
||||
expectedCode = 204,
|
||||
) => {
|
||||
return app.request
|
||||
.delete(
|
||||
`/api/admin/projects/default/features/${featureName}/link/${linkId}`,
|
||||
)
|
||||
.expect(expectedCode);
|
||||
};
|
||||
|
||||
test('should manage feature links', async () => {
|
||||
await app.createFeature('my_feature');
|
||||
|
||||
await addLink('my_feature', { url: 'example.com', title: 'feature link' });
|
||||
|
||||
const links = await featureLinkStore.getAll();
|
||||
expect(links).toMatchObject([
|
||||
{
|
||||
url: 'example.com',
|
||||
title: 'feature link',
|
||||
featureName: 'my_feature',
|
||||
},
|
||||
]);
|
||||
|
||||
await updatedLink('my_feature', links[0].id, {
|
||||
url: 'example_updated.com',
|
||||
title: 'feature link updated',
|
||||
});
|
||||
|
||||
const updatedLinks = await featureLinkStore.getAll();
|
||||
expect(updatedLinks).toMatchObject([
|
||||
{
|
||||
url: 'example_updated.com',
|
||||
title: 'feature link updated',
|
||||
featureName: 'my_feature',
|
||||
},
|
||||
]);
|
||||
|
||||
await deleteLink('my_feature', links[0].id);
|
||||
|
||||
const deletedLinks = await featureLinkStore.getAll();
|
||||
expect(deletedLinks).toMatchObject([]);
|
||||
|
||||
const [event1, event2, event3] = await eventStore.getEvents();
|
||||
expect([event1, event2, event3]).toMatchObject([
|
||||
{
|
||||
type: 'feature-link-removed',
|
||||
data: null,
|
||||
preData: {
|
||||
url: 'example_updated.com',
|
||||
title: 'feature link updated',
|
||||
},
|
||||
featureName: 'my_feature',
|
||||
project: 'default',
|
||||
},
|
||||
{
|
||||
type: 'feature-link-updated',
|
||||
data: { url: 'example_updated.com', title: 'feature link updated' },
|
||||
preData: { url: 'example.com', title: 'feature link' },
|
||||
featureName: 'my_feature',
|
||||
project: 'default',
|
||||
},
|
||||
{
|
||||
type: 'feature-link-added',
|
||||
data: { url: 'example.com', title: 'feature link' },
|
||||
preData: null,
|
||||
featureName: 'my_feature',
|
||||
project: 'default',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -72,6 +72,7 @@ import {
|
||||
createFakeProjectService,
|
||||
createFakeUserSubscriptionsService,
|
||||
createFeatureLifecycleService,
|
||||
createFeatureLinkService,
|
||||
createFeatureToggleService,
|
||||
createProjectService,
|
||||
createUserSubscriptionsService,
|
||||
@ -425,9 +426,11 @@ export const createServices = (
|
||||
? withTransactional(createUserSubscriptionsService(config), db)
|
||||
: withFakeTransactional(createFakeUserSubscriptionsService(config));
|
||||
|
||||
const transactionalFeatureLinkService = withFakeTransactional(
|
||||
createFakeFeatureLinkService(config).featureLinkService,
|
||||
);
|
||||
const transactionalFeatureLinkService = db
|
||||
? withTransactional(createFeatureLinkService(config), db)
|
||||
: withFakeTransactional(
|
||||
createFakeFeatureLinkService(config).featureLinkService,
|
||||
);
|
||||
|
||||
return {
|
||||
transactionalAccessService,
|
||||
|
Loading…
Reference in New Issue
Block a user