mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: add event search endpoint (#1893)
* feat: add event search endpoint * refactor: expand variable names * refactor: add table type to query builder * refactor: improve schema limit/offset types * refactor: describe searchEventsSchema fields
This commit is contained in:
		
							parent
							
								
									49095025ff
								
							
						
					
					
						commit
						a34c674971
					
				| @ -24,7 +24,7 @@ test('Trying to get events by name if db fails should yield empty list', async ( | ||||
|         client: 'pg', | ||||
|     }); | ||||
|     const store = new EventStore(db, getLogger); | ||||
|     const events = await store.getEventsFilterByType('application-created'); | ||||
|     const events = await store.searchEvents({ type: 'application-created' }); | ||||
|     expect(events).toBeTruthy(); | ||||
|     expect(events.length).toBe(0); | ||||
| }); | ||||
|  | ||||
| @ -1,9 +1,10 @@ | ||||
| import { EventEmitter } from 'events'; | ||||
| import { Knex } from 'knex'; | ||||
| import { DROP_FEATURES, IEvent, IBaseEvent } from '../types/events'; | ||||
| import { IEvent, IBaseEvent } from '../types/events'; | ||||
| import { LogProvider, Logger } from '../logger'; | ||||
| import { IEventStore } from '../types/stores/event-store'; | ||||
| import { ITag } from '../types/model'; | ||||
| import { SearchEventsSchema } from '../openapi/spec/search-events-schema'; | ||||
| 
 | ||||
| const EVENT_COLUMNS = [ | ||||
|     'id', | ||||
| @ -115,50 +116,44 @@ class EventStore extends EventEmitter implements IEventStore { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async getEventsFilterByType(name: string): Promise<IEvent[]> { | ||||
|         try { | ||||
|             const rows = await this.db | ||||
|                 .select(EVENT_COLUMNS) | ||||
|                 .from(TABLE) | ||||
|                 .limit(100) | ||||
|                 .where('type', name) | ||||
|                 .andWhere( | ||||
|                     'id', | ||||
|                     '>=', | ||||
|                     this.db | ||||
|                         .select(this.db.raw('coalesce(max(id),0) as id')) | ||||
|                         .from(TABLE) | ||||
|                         .where({ type: DROP_FEATURES }), | ||||
|                 ) | ||||
|                 .orderBy('created_at', 'desc'); | ||||
|             return rows.map(this.rowToEvent); | ||||
|         } catch (err) { | ||||
|             this.logger.error(err); | ||||
|             return []; | ||||
|         } | ||||
|     } | ||||
|     async searchEvents(search: SearchEventsSchema = {}): Promise<IEvent[]> { | ||||
|         let query = this.db | ||||
|             .select(EVENT_COLUMNS) | ||||
|             .from<IEventTable>(TABLE) | ||||
|             .limit(search.limit ?? 100) | ||||
|             .offset(search.offset ?? 0) | ||||
|             .orderBy('created_at', 'desc'); | ||||
| 
 | ||||
|     async getEventsFilterByProject(project: string): Promise<IEvent[]> { | ||||
|         try { | ||||
|             const rows = await this.db | ||||
|                 .select(EVENT_COLUMNS) | ||||
|                 .from(TABLE) | ||||
|                 .where({ project }) | ||||
|                 .orderBy('created_at', 'desc'); | ||||
|             return rows.map(this.rowToEvent); | ||||
|         } catch (err) { | ||||
|             return []; | ||||
|         if (search.type) { | ||||
|             query = query.andWhere({ | ||||
|                 type: search.type, | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         if (search.project) { | ||||
|             query = query.andWhere({ | ||||
|                 project: search.project, | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         if (search.feature) { | ||||
|             query = query.andWhere({ | ||||
|                 feature_name: search.feature, | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         if (search.query) { | ||||
|             query = query.where((where) => | ||||
|                 where | ||||
|                     .orWhereRaw('type::text ILIKE ?', `%${search.query}%`) | ||||
|                     .orWhereRaw('created_by::text ILIKE ?', `%${search.query}%`) | ||||
|                     .orWhereRaw('data::text ILIKE ?', `%${search.query}%`) | ||||
|                     .orWhereRaw('pre_data::text ILIKE ?', `%${search.query}%`), | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async getEventsForFeature(featureName: string): Promise<IEvent[]> { | ||||
|         try { | ||||
|             const rows = await this.db | ||||
|                 .select(EVENT_COLUMNS) | ||||
|                 .from(TABLE) | ||||
|                 .where({ feature_name: featureName }) | ||||
|                 .orderBy('created_at', 'desc'); | ||||
|             return rows.map(this.rowToEvent); | ||||
|             return (await query).map(this.rowToEvent); | ||||
|         } catch (err) { | ||||
|             return []; | ||||
|         } | ||||
|  | ||||
| @ -104,6 +104,7 @@ import { groupSchema } from './spec/group-schema'; | ||||
| import { groupsSchema } from './spec/groups-schema'; | ||||
| import { groupUserModelSchema } from './spec/group-user-model-schema'; | ||||
| import { usersGroupsBaseSchema } from './spec/users-groups-base-schema'; | ||||
| import { searchEventsSchema } from './spec/search-events-schema'; | ||||
| 
 | ||||
| // All schemas in `openapi/spec` should be listed here.
 | ||||
| export const schemas = { | ||||
| @ -178,6 +179,7 @@ export const schemas = { | ||||
|     resetPasswordSchema, | ||||
|     roleSchema, | ||||
|     sdkContextSchema, | ||||
|     searchEventsSchema, | ||||
|     segmentSchema, | ||||
|     setStrategySortOrderSchema, | ||||
|     sortOrderSchema, | ||||
|  | ||||
| @ -6,7 +6,7 @@ export const featureEventsSchema = { | ||||
|     $id: '#/components/schemas/featureEventsSchema', | ||||
|     type: 'object', | ||||
|     additionalProperties: false, | ||||
|     required: ['toggleName', 'events'], | ||||
|     required: ['events'], | ||||
|     properties: { | ||||
|         version: { type: 'number' }, | ||||
|         toggleName: { | ||||
|  | ||||
							
								
								
									
										47
									
								
								src/lib/openapi/spec/search-events-schema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/lib/openapi/spec/search-events-schema.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| import { FromSchema } from 'json-schema-to-ts'; | ||||
| 
 | ||||
| export const searchEventsSchema = { | ||||
|     $id: '#/components/schemas/searchEventsSchema', | ||||
|     type: 'object', | ||||
|     description: ` | ||||
|         Search for events by type, project, feature, free-text query, | ||||
|         or a combination thereof. Pass an empty object to fetch all events. | ||||
|     `,
 | ||||
|     properties: { | ||||
|         type: { | ||||
|             type: 'string', | ||||
|             description: 'Find events by event type (case-sensitive).', | ||||
|         }, | ||||
|         project: { | ||||
|             type: 'string', | ||||
|             description: 'Find events by project ID (case-sensitive).', | ||||
|         }, | ||||
|         feature: { | ||||
|             type: 'string', | ||||
|             description: 'Find events by feature toggle name (case-sensitive).', | ||||
|         }, | ||||
|         query: { | ||||
|             type: 'string', | ||||
|             description: ` | ||||
|                 Find events by a free-text search query. | ||||
|                 The query will be matched against the event type, | ||||
|                 the username or email that created the event (if any), | ||||
|                 and the event data payload (if any). | ||||
|             `,
 | ||||
|         }, | ||||
|         limit: { | ||||
|             type: 'integer', | ||||
|             minimum: 1, | ||||
|             maximum: 100, | ||||
|             default: 100, | ||||
|         }, | ||||
|         offset: { | ||||
|             type: 'integer', | ||||
|             minimum: 0, | ||||
|             default: 0, | ||||
|         }, | ||||
|     }, | ||||
|     components: {}, | ||||
| } as const; | ||||
| 
 | ||||
| export type SearchEventsSchema = FromSchema<typeof searchEventsSchema>; | ||||
| @ -19,6 +19,8 @@ import { | ||||
|     FeatureEventsSchema, | ||||
| } from '../../../lib/openapi/spec/feature-events-schema'; | ||||
| import { getStandardResponses } from '../../../lib/openapi/util/standard-responses'; | ||||
| import { createRequestSchema } from '../../openapi/util/create-request-schema'; | ||||
| import { SearchEventsSchema } from '../../openapi/spec/search-events-schema'; | ||||
| 
 | ||||
| const version = 1; | ||||
| export default class EventController extends Controller { | ||||
| @ -86,9 +88,24 @@ export default class EventController extends Controller { | ||||
|                 }), | ||||
|             ], | ||||
|         }); | ||||
| 
 | ||||
|         this.route({ | ||||
|             method: 'post', | ||||
|             path: '/search', | ||||
|             handler: this.searchEvents, | ||||
|             permission: NONE, | ||||
|             middleware: [ | ||||
|                 openApiService.validPath({ | ||||
|                     operationId: 'searchEvents', | ||||
|                     tags: ['admin'], | ||||
|                     requestBody: createRequestSchema('searchEventsSchema'), | ||||
|                     responses: { 200: createResponseSchema('eventsSchema') }, | ||||
|                 }), | ||||
|             ], | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     fixEvents(events: IEvent[]): IEvent[] { | ||||
|     maybeAnonymiseEvents(events: IEvent[]): IEvent[] { | ||||
|         if (this.anonymise) { | ||||
|             return events.map((e: IEvent) => ({ | ||||
|                 ...e, | ||||
| @ -105,15 +122,16 @@ export default class EventController extends Controller { | ||||
|         const { project } = req.query; | ||||
|         let events: IEvent[]; | ||||
|         if (project) { | ||||
|             events = await this.eventService.getEventsForProject(project); | ||||
|             events = await this.eventService.searchEvents({ project }); | ||||
|         } else { | ||||
|             events = await this.eventService.getEvents(); | ||||
|         } | ||||
| 
 | ||||
|         const response: EventsSchema = { | ||||
|             version, | ||||
|             events: serializeDates(this.fixEvents(events)), | ||||
|             events: serializeDates(this.maybeAnonymiseEvents(events)), | ||||
|         }; | ||||
| 
 | ||||
|         this.openApiService.respondWithValidation( | ||||
|             200, | ||||
|             res, | ||||
| @ -126,13 +144,32 @@ export default class EventController extends Controller { | ||||
|         req: Request<{ featureName: string }>, | ||||
|         res: Response<FeatureEventsSchema>, | ||||
|     ): Promise<void> { | ||||
|         const toggleName = req.params.featureName; | ||||
|         const events = await this.eventService.getEventsForToggle(toggleName); | ||||
|         const feature = req.params.featureName; | ||||
|         const events = await this.eventService.searchEvents({ feature }); | ||||
| 
 | ||||
|         const response = { | ||||
|             version, | ||||
|             toggleName, | ||||
|             events: serializeDates(this.fixEvents(events)), | ||||
|             toggleName: feature, | ||||
|             events: serializeDates(this.maybeAnonymiseEvents(events)), | ||||
|         }; | ||||
| 
 | ||||
|         this.openApiService.respondWithValidation( | ||||
|             200, | ||||
|             res, | ||||
|             featureEventsSchema.$id, | ||||
|             response, | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     async searchEvents( | ||||
|         req: Request<unknown, unknown, SearchEventsSchema>, | ||||
|         res: Response<EventsSchema>, | ||||
|     ): Promise<void> { | ||||
|         const events = await this.eventService.searchEvents(req.body); | ||||
| 
 | ||||
|         const response = { | ||||
|             version, | ||||
|             events: serializeDates(this.maybeAnonymiseEvents(events)), | ||||
|         }; | ||||
| 
 | ||||
|         this.openApiService.respondWithValidation( | ||||
|  | ||||
| @ -3,6 +3,7 @@ import { IUnleashStores } from '../types/stores'; | ||||
| import { Logger } from '../logger'; | ||||
| import { IEventStore } from '../types/stores/event-store'; | ||||
| import { IEvent } from '../types/events'; | ||||
| import { SearchEventsSchema } from '../openapi/spec/search-events-schema'; | ||||
| 
 | ||||
| export default class EventService { | ||||
|     private logger: Logger; | ||||
| @ -21,12 +22,8 @@ export default class EventService { | ||||
|         return this.eventStore.getEvents(); | ||||
|     } | ||||
| 
 | ||||
|     async getEventsForToggle(name: string): Promise<IEvent[]> { | ||||
|         return this.eventStore.getEventsForFeature(name); | ||||
|     } | ||||
| 
 | ||||
|     async getEventsForProject(project: string): Promise<IEvent[]> { | ||||
|         return this.eventStore.getEventsFilterByProject(project); | ||||
|     async searchEvents(search: SearchEventsSchema): Promise<IEvent[]> { | ||||
|         return this.eventStore.searchEvents(search); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,12 +1,11 @@ | ||||
| import EventEmitter from 'events'; | ||||
| import { IBaseEvent, IEvent } from '../events'; | ||||
| import { Store } from './store'; | ||||
| import { SearchEventsSchema } from '../../openapi/spec/search-events-schema'; | ||||
| 
 | ||||
| export interface IEventStore extends Store<IEvent, number>, EventEmitter { | ||||
|     store(event: IBaseEvent): Promise<void>; | ||||
|     batchStore(events: IBaseEvent[]): Promise<void>; | ||||
|     getEvents(): Promise<IEvent[]>; | ||||
|     getEventsFilterByType(name: string): Promise<IEvent[]>; | ||||
|     getEventsForFeature(featureName: string): Promise<IEvent[]>; | ||||
|     getEventsFilterByProject(project: string): Promise<IEvent[]>; | ||||
|     searchEvents(search: SearchEventsSchema): Promise<IEvent[]>; | ||||
| } | ||||
|  | ||||
| @ -1,8 +1,9 @@ | ||||
| import { IUnleashTest, setupApp } from '../../helpers/test-helper'; | ||||
| import dbInit, { ITestDb } from '../../helpers/database-init'; | ||||
| import getLogger from '../../../fixtures/no-logger'; | ||||
| import { FEATURE_CREATED } from '../../../../lib/types/events'; | ||||
| import { FEATURE_CREATED, IBaseEvent } from '../../../../lib/types/events'; | ||||
| import { IEventStore } from '../../../../lib/types/stores/event-store'; | ||||
| import { randomId } from '../../../../lib/util/random-id'; | ||||
| 
 | ||||
| let app: IUnleashTest; | ||||
| let db: ITestDb; | ||||
| @ -14,6 +15,10 @@ beforeAll(async () => { | ||||
|     eventStore = db.stores.eventStore; | ||||
| }); | ||||
| 
 | ||||
| beforeEach(async () => { | ||||
|     await eventStore.deleteAll(); | ||||
| }); | ||||
| 
 | ||||
| afterAll(async () => { | ||||
|     await app.destroy(); | ||||
|     await db.destroy(); | ||||
| @ -60,3 +65,61 @@ test('Can filter by project', async () => { | ||||
|             expect(res.body.events[0].data.id).toEqual('feature'); | ||||
|         }); | ||||
| }); | ||||
| 
 | ||||
| test('can search for events', async () => { | ||||
|     const events: IBaseEvent[] = [ | ||||
|         { | ||||
|             type: FEATURE_CREATED, | ||||
|             project: randomId(), | ||||
|             data: { id: randomId() }, | ||||
|             tags: [], | ||||
|             createdBy: randomId(), | ||||
|         }, | ||||
|         { | ||||
|             type: FEATURE_CREATED, | ||||
|             project: randomId(), | ||||
|             data: { id: randomId() }, | ||||
|             preData: { id: randomId() }, | ||||
|             tags: [], | ||||
|             createdBy: randomId(), | ||||
|         }, | ||||
|     ]; | ||||
| 
 | ||||
|     await Promise.all( | ||||
|         events.map((event) => { | ||||
|             return eventStore.store(event); | ||||
|         }), | ||||
|     ); | ||||
| 
 | ||||
|     await app.request | ||||
|         .post('/api/admin/events/search') | ||||
|         .send({}) | ||||
|         .expect(200) | ||||
|         .expect((res) => { | ||||
|             expect(res.body.events).toHaveLength(2); | ||||
|         }); | ||||
|     await app.request | ||||
|         .post('/api/admin/events/search') | ||||
|         .send({ limit: 1, offset: 1 }) | ||||
|         .expect(200) | ||||
|         .expect((res) => { | ||||
|             expect(res.body.events).toHaveLength(1); | ||||
|             expect(res.body.events[0].data.id).toEqual(events[0].data.id); | ||||
|         }); | ||||
|     await app.request | ||||
|         .post('/api/admin/events/search') | ||||
|         .send({ query: events[1].data.id }) | ||||
|         .expect(200) | ||||
|         .expect((res) => { | ||||
|             expect(res.body.events).toHaveLength(1); | ||||
|             expect(res.body.events[0].data.id).toEqual(events[1].data.id); | ||||
|         }); | ||||
|     await app.request | ||||
|         .post('/api/admin/events/search') | ||||
|         .send({ query: events[1].preData.id }) | ||||
|         .expect(200) | ||||
|         .expect((res) => { | ||||
|             expect(res.body.events).toHaveLength(1); | ||||
|             expect(res.body.events[0].preData.id).toEqual(events[1].preData.id); | ||||
|         }); | ||||
| }); | ||||
|  | ||||
| @ -1010,7 +1010,6 @@ Object { | ||||
|           }, | ||||
|         }, | ||||
|         "required": Array [ | ||||
|           "toggleName", | ||||
|           "events", | ||||
|         ], | ||||
|         "type": "object", | ||||
| @ -2218,6 +2217,47 @@ Object { | ||||
|         ], | ||||
|         "type": "object", | ||||
|       }, | ||||
|       "searchEventsSchema": Object { | ||||
|         "description": " | ||||
|         Search for events by type, project, feature, free-text query, | ||||
|         or a combination thereof. Pass an empty object to fetch all events. | ||||
|     ", | ||||
|         "properties": Object { | ||||
|           "feature": Object { | ||||
|             "description": "Find events by feature toggle name (case-sensitive).", | ||||
|             "type": "string", | ||||
|           }, | ||||
|           "limit": Object { | ||||
|             "default": 100, | ||||
|             "maximum": 100, | ||||
|             "minimum": 1, | ||||
|             "type": "integer", | ||||
|           }, | ||||
|           "offset": Object { | ||||
|             "default": 0, | ||||
|             "minimum": 0, | ||||
|             "type": "integer", | ||||
|           }, | ||||
|           "project": Object { | ||||
|             "description": "Find events by project ID (case-sensitive).", | ||||
|             "type": "string", | ||||
|           }, | ||||
|           "query": Object { | ||||
|             "description": " | ||||
|                 Find events by a free-text search query. | ||||
|                 The query will be matched against the event type, | ||||
|                 the username or email that created the event (if any), | ||||
|                 and the event data payload (if any). | ||||
|             ", | ||||
|             "type": "string", | ||||
|           }, | ||||
|           "type": Object { | ||||
|             "description": "Find events by event type (case-sensitive).", | ||||
|             "type": "string", | ||||
|           }, | ||||
|         }, | ||||
|         "type": "object", | ||||
|       }, | ||||
|       "segmentSchema": Object { | ||||
|         "additionalProperties": false, | ||||
|         "properties": Object { | ||||
| @ -3736,6 +3776,37 @@ If the provided project does not exist, the list of events will be empty.", | ||||
|         ], | ||||
|       }, | ||||
|     }, | ||||
|     "/api/admin/events/search": Object { | ||||
|       "post": Object { | ||||
|         "operationId": "searchEvents", | ||||
|         "requestBody": Object { | ||||
|           "content": Object { | ||||
|             "application/json": Object { | ||||
|               "schema": Object { | ||||
|                 "$ref": "#/components/schemas/searchEventsSchema", | ||||
|               }, | ||||
|             }, | ||||
|           }, | ||||
|           "description": "searchEventsSchema", | ||||
|           "required": true, | ||||
|         }, | ||||
|         "responses": Object { | ||||
|           "200": Object { | ||||
|             "content": Object { | ||||
|               "application/json": Object { | ||||
|                 "schema": Object { | ||||
|                   "$ref": "#/components/schemas/eventsSchema", | ||||
|                 }, | ||||
|               }, | ||||
|             }, | ||||
|             "description": "eventsSchema", | ||||
|           }, | ||||
|         }, | ||||
|         "tags": Array [ | ||||
|           "admin", | ||||
|         ], | ||||
|       }, | ||||
|     }, | ||||
|     "/api/admin/events/{featureName}": Object { | ||||
|       "get": Object { | ||||
|         "description": "Returns all events related to the specified feature toggle. If the feature toggle does not exist, the list of events will be empty.", | ||||
|  | ||||
| @ -32,9 +32,9 @@ test('Can create new setting', async () => { | ||||
| 
 | ||||
|     expect(actual).toStrictEqual(someData); | ||||
|     const { eventStore } = stores; | ||||
|     const createdEvents = await eventStore.getEventsFilterByType( | ||||
|         SETTING_CREATED, | ||||
|     ); | ||||
|     const createdEvents = await eventStore.searchEvents({ | ||||
|         type: SETTING_CREATED, | ||||
|     }); | ||||
|     expect(createdEvents).toHaveLength(1); | ||||
| }); | ||||
| 
 | ||||
| @ -46,9 +46,9 @@ test('Can delete setting', async () => { | ||||
|     const actual = await service.get('some-setting'); | ||||
|     expect(actual).toBeUndefined(); | ||||
|     const { eventStore } = stores; | ||||
|     const createdEvents = await eventStore.getEventsFilterByType( | ||||
|         SETTING_DELETED, | ||||
|     ); | ||||
|     const createdEvents = await eventStore.searchEvents({ | ||||
|         type: SETTING_DELETED, | ||||
|     }); | ||||
|     expect(createdEvents).toHaveLength(1); | ||||
| }); | ||||
| 
 | ||||
| @ -61,8 +61,8 @@ test('Can update setting', async () => { | ||||
|         { ...someData, test: 'fun' }, | ||||
|         'test-user', | ||||
|     ); | ||||
|     const updatedEvents = await eventStore.getEventsFilterByType( | ||||
|         SETTING_UPDATED, | ||||
|     ); | ||||
|     const updatedEvents = await eventStore.searchEvents({ | ||||
|         type: SETTING_UPDATED, | ||||
|     }); | ||||
|     expect(updatedEvents).toHaveLength(1); | ||||
| }); | ||||
|  | ||||
| @ -209,12 +209,12 @@ test('Should get all events of type', async () => { | ||||
|             return eventStore.store(event); | ||||
|         }), | ||||
|     ); | ||||
|     const featureCreatedEvents = await eventStore.getEventsFilterByType( | ||||
|         FEATURE_CREATED, | ||||
|     ); | ||||
|     const featureCreatedEvents = await eventStore.searchEvents({ | ||||
|         type: FEATURE_CREATED, | ||||
|     }); | ||||
|     expect(featureCreatedEvents).toHaveLength(3); | ||||
|     const featureDeletedEvents = await eventStore.getEventsFilterByType( | ||||
|         FEATURE_DELETED, | ||||
|     ); | ||||
|     const featureDeletedEvents = await eventStore.searchEvents({ | ||||
|         type: FEATURE_DELETED, | ||||
|     }); | ||||
|     expect(featureDeletedEvents).toHaveLength(3); | ||||
| }); | ||||
|  | ||||
							
								
								
									
										12
									
								
								src/test/fixtures/fake-event-store.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								src/test/fixtures/fake-event-store.ts
									
									
									
									
										vendored
									
									
								
							| @ -11,10 +11,6 @@ class FakeEventStore extends EventEmitter implements IEventStore { | ||||
|         this.events = []; | ||||
|     } | ||||
| 
 | ||||
|     async getEventsForFeature(featureName: string): Promise<IEvent[]> { | ||||
|         return this.events.filter((e) => e.featureName === featureName); | ||||
|     } | ||||
| 
 | ||||
|     store(event: IEvent): Promise<void> { | ||||
|         this.events.push(event); | ||||
|         this.emit(event.type, event); | ||||
| @ -58,12 +54,8 @@ class FakeEventStore extends EventEmitter implements IEventStore { | ||||
|         return this.events; | ||||
|     } | ||||
| 
 | ||||
|     async getEventsFilterByType(type: string): Promise<IEvent[]> { | ||||
|         return this.events.filter((e) => e.type === type); | ||||
|     } | ||||
| 
 | ||||
|     async getEventsFilterByProject(project: string): Promise<IEvent[]> { | ||||
|         return this.events.filter((e) => e.project === project); | ||||
|     async searchEvents(): Promise<IEvent[]> { | ||||
|         throw new Error('Method not implemented.'); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user