mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01: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