diff --git a/src/lib/features/events/event-service.ts b/src/lib/features/events/event-service.ts index bebf0f4569..2a05565a83 100644 --- a/src/lib/features/events/event-service.ts +++ b/src/lib/features/events/event-service.ts @@ -147,19 +147,31 @@ export default class EventService { const queryParams: IQueryParam[] = []; if (params.createdAtFrom) { - queryParams.push({ - field: 'created_at', - operator: 'IS_ON_OR_AFTER', - values: [params.createdAtFrom], - }); + const parsed = parseSearchOperatorValue( + 'created_at', + params.createdAtFrom, + ); + if (parsed) { + queryParams.push({ + field: parsed.field, + operator: 'IS_ON_OR_AFTER', + values: parsed.values, + }); + } } if (params.createdAtTo) { - queryParams.push({ - field: 'created_at', - operator: 'IS_BEFORE', - values: [params.createdAtTo], - }); + const parsed = parseSearchOperatorValue( + 'created_at', + params.createdAtTo, + ); + if (parsed) { + queryParams.push({ + field: parsed.field, + operator: 'IS_BEFORE', + values: parsed.values, + }); + } } if (params.createdBy) { @@ -170,12 +182,15 @@ export default class EventService { if (parsed) queryParams.push(parsed); } - if (params.type) { - const parsed = parseSearchOperatorValue('type', params.type); + if (params.feature) { + const parsed = parseSearchOperatorValue( + 'feature_name', + params.feature, + ); if (parsed) queryParams.push(parsed); } - ['feature', 'project'].forEach((field) => { + ['project', 'type'].forEach((field) => { if (params[field]) { const parsed = parseSearchOperatorValue(field, params[field]); if (parsed) queryParams.push(parsed); diff --git a/src/lib/types/stores/event-store.ts b/src/lib/types/stores/event-store.ts index 52ac5e9eca..afbd53076b 100644 --- a/src/lib/types/stores/event-store.ts +++ b/src/lib/types/stores/event-store.ts @@ -8,6 +8,7 @@ import type { IQueryParam } from '../../features/feature-toggle/types/feature-to export interface IEventSearchParams { project?: string; query?: string; + feature?: string; createdAtFrom?: string; createdAtTo?: string; createdBy?: string; diff --git a/src/test/e2e/api/admin/event-search.e2e.test.ts b/src/test/e2e/api/admin/event-search.e2e.test.ts index 1a43783818..5378a18f01 100644 --- a/src/test/e2e/api/admin/event-search.e2e.test.ts +++ b/src/test/e2e/api/admin/event-search.e2e.test.ts @@ -87,3 +87,279 @@ test('should search events by query', async () => { total: 1, }); }); + +test('should filter events by feature', async () => { + await eventService.storeEvent({ + type: FEATURE_CREATED, + project: 'default', + featureName: 'my_feature_a', + createdBy: 'test-user', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + await eventService.storeEvent({ + type: FEATURE_CREATED, + project: 'default', + featureName: 'my_feature_b', + createdBy: 'test-user', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + const { body } = await searchEvents({ feature: 'IS:my_feature_b' }); + + expect(body).toMatchObject({ + events: [ + { + type: 'feature-created', + featureName: 'my_feature_b', + }, + ], + total: 1, + }); +}); + +test('should filter events by project', async () => { + await eventService.storeEvent({ + type: FEATURE_CREATED, + project: 'default', + createdBy: 'test-user', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + await eventService.storeEvent({ + type: FEATURE_CREATED, + project: 'another_project', + createdBy: 'test-user', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + const { body } = await searchEvents({ project: 'IS:another_project' }); + + expect(body).toMatchObject({ + events: [ + { + type: 'feature-created', + project: 'another_project', + }, + ], + total: 1, + }); +}); + +test('should filter events by type', async () => { + await eventService.storeEvent({ + type: 'change-added', + project: 'default', + createdBy: 'test-user', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + await eventService.storeEvent({ + type: 'feature-created', + project: 'default', + createdBy: 'test-user', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + const { body } = await searchEvents({ type: 'IS:change-added' }); + + expect(body).toMatchObject({ + events: [ + { + type: 'change-added', + }, + ], + total: 1, + }); +}); + +test('should filter events by created by', async () => { + await eventService.storeEvent({ + type: FEATURE_CREATED, + createdBy: 'admin1@example.com', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + await eventService.storeEvent({ + type: FEATURE_CREATED, + createdBy: 'admin2@example.com', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + const { body } = await searchEvents({ createdBy: 'IS:admin2@example.com' }); + + expect(body).toMatchObject({ + events: [ + { + createdBy: 'admin2@example.com', + }, + ], + total: 1, + }); +}); + +test('should filter events by created date range', async () => { + await eventService.storeEvent({ + type: FEATURE_CREATED, + project: 'default', + data: { featureName: 'my_feature_a' }, + createdBy: 'test-user', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + await eventService.storeEvent({ + type: FEATURE_CREATED, + project: 'default', + data: { featureName: 'my_feature_b' }, + createdBy: 'test-user', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + const today = new Date(); + + const { body } = await searchEvents({ + createdAtFrom: `IS:${today.toISOString().split('T')[0]}`, + }); + + expect(body).toMatchObject({ + events: [ + { + type: FEATURE_CREATED, + data: { featureName: 'my_feature_b' }, + }, + { + type: FEATURE_CREATED, + data: { featureName: 'my_feature_a' }, + }, + ], + total: 2, + }); +}); + +test('should paginate with offset and limit', async () => { + for (let i = 0; i < 5; i++) { + await eventService.storeEvent({ + type: FEATURE_CREATED, + project: 'default', + data: { featureName: `my_feature_${i}` }, + createdBy: 'test-user', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + } + + const { body: secondPage } = await searchEvents({ + offset: '2', + limit: '2', + }); + + expect(secondPage.events).toMatchObject([ + { + data: { featureName: `my_feature_2` }, + }, + { + data: { featureName: `my_feature_1` }, + }, + ]); +}); + +test('should filter events by feature using IS_ANY_OF', async () => { + await eventService.storeEvent({ + type: FEATURE_CREATED, + project: 'default', + featureName: 'my_feature_a', + createdBy: 'test-user', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + await eventService.storeEvent({ + type: FEATURE_CREATED, + project: 'default', + featureName: 'my_feature_b', + createdBy: 'test-user', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + await eventService.storeEvent({ + type: FEATURE_CREATED, + project: 'default', + featureName: 'my_feature_c', + createdBy: 'test-user', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + const { body } = await searchEvents({ + feature: 'IS_ANY_OF:my_feature_a,my_feature_b', + }); + + expect(body).toMatchObject({ + events: [ + { + type: 'feature-created', + featureName: 'my_feature_b', + }, + { + type: 'feature-created', + featureName: 'my_feature_a', + }, + ], + total: 2, + }); +}); + +test('should filter events by project using IS_ANY_OF', async () => { + await eventService.storeEvent({ + type: FEATURE_CREATED, + project: 'project_a', + createdBy: 'test-user', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + await eventService.storeEvent({ + type: FEATURE_CREATED, + project: 'project_b', + createdBy: 'test-user', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + await eventService.storeEvent({ + type: FEATURE_CREATED, + project: 'project_c', + createdBy: 'test-user', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + const { body } = await searchEvents({ + project: 'IS_ANY_OF:project_a,project_b', + }); + + expect(body).toMatchObject({ + events: [ + { + type: 'feature-created', + project: 'project_b', + }, + { + type: 'feature-created', + project: 'project_a', + }, + ], + total: 2, + }); +});