diff --git a/frontend/src/component/events/EventLog/EventLogFilters.tsx b/frontend/src/component/events/EventLog/EventLogFilters.tsx index b126383bc1..b0ffa3c7a7 100644 --- a/frontend/src/component/events/EventLog/EventLogFilters.tsx +++ b/frontend/src/component/events/EventLog/EventLogFilters.tsx @@ -9,13 +9,16 @@ import { useFeatureSearch } from 'hooks/api/getters/useFeatureSearch/useFeatureS import { EventSchemaType, type FeatureSearchResponseSchema } from 'openapi'; import type { ProjectSchema } from 'openapi'; import { useEventCreators } from 'hooks/api/getters/useEventCreators/useEventCreators'; +import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments'; export const useEventLogFilters = ( projects: ProjectSchema[], features: FeatureSearchResponseSchema[], ) => { + const { environments } = useEnvironments(); const { eventCreators } = useEventCreators(); const [availableFilters, setAvailableFilters] = useState([]); + useEffect(() => { const projectOptions = projects?.map((project: ProjectSchema) => ({ @@ -41,6 +44,12 @@ export const useEventLogFilters = ( }), ); + const environmentOptions = + environments?.map((env) => ({ + label: env.name, + value: env.name, + })) ?? []; + const availableFilters: IFilterItem[] = [ { label: 'Date From', @@ -102,6 +111,18 @@ export const useEventLogFilters = ( }, ] as IFilterItem[]) : []), + ...(environmentOptions.length > 0 + ? ([ + { + label: 'Environment', + icon: 'cloud', + options: environmentOptions, + filterKey: 'environment', + singularOperators: ['IS'], + pluralOperators: ['IS_ANY_OF'], + }, + ] as IFilterItem[]) + : []), ]; setAvailableFilters(availableFilters); @@ -109,6 +130,7 @@ export const useEventLogFilters = ( JSON.stringify(features), JSON.stringify(projects), JSON.stringify(eventCreators), + JSON.stringify(environments), ]); return availableFilters; diff --git a/frontend/src/component/events/EventLog/useEventLogSearch.ts b/frontend/src/component/events/EventLog/useEventLogSearch.ts index 49485097bf..b398c18335 100644 --- a/frontend/src/component/events/EventLog/useEventLogSearch.ts +++ b/frontend/src/component/events/EventLog/useEventLogSearch.ts @@ -71,6 +71,7 @@ export const useEventLogSearch = ( }), createdBy: FilterItemParam, type: FilterItemParam, + environment: FilterItemParam, ...extraParameters(logType), }; diff --git a/src/lib/features/events/event-service.ts b/src/lib/features/events/event-service.ts index 913eed5f14..9f8e67aedf 100644 --- a/src/lib/features/events/event-service.ts +++ b/src/lib/features/events/event-service.ts @@ -213,7 +213,6 @@ export default class EventService { representation: 'date', }), ); - queryParams.push({ field: parsed.field, operator: 'IS_BEFORE', @@ -238,7 +237,7 @@ export default class EventService { if (parsed) queryParams.push(parsed); } - ['project', 'type'].forEach((field) => { + ['project', 'type', 'environment'].forEach((field) => { if (params[field]) { const parsed = parseSearchOperatorValue(field, params[field]); if (parsed) queryParams.push(parsed); diff --git a/src/lib/openapi/spec/event-search-query-parameters.ts b/src/lib/openapi/spec/event-search-query-parameters.ts index 4da5b92203..04d7760f3d 100644 --- a/src/lib/openapi/spec/event-search-query-parameters.ts +++ b/src/lib/openapi/spec/event-search-query-parameters.ts @@ -100,6 +100,17 @@ export const eventSearchQueryParameters = [ 'The number of feature environments to return in a page. By default it is set to 50. The maximum is 1000.', in: 'query', }, + { + name: 'environment', + schema: { + type: 'string', + example: 'IS:production', + pattern: '^(IS|IS_ANY_OF):(.*?)(,([a-zA-Z0-9_]+))*$', + }, + description: + 'Filter by environment name using supported operators: IS, IS_ANY_OF.', + in: 'query', + }, ] as const; export type EventSearchQueryParameters = Partial< diff --git a/src/lib/types/stores/event-store.ts b/src/lib/types/stores/event-store.ts index 2f66a41c39..ad010dd96e 100644 --- a/src/lib/types/stores/event-store.ts +++ b/src/lib/types/stores/event-store.ts @@ -16,6 +16,7 @@ export interface IEventSearchParams { to?: string; createdBy?: string; type?: string; + environment?: string; offset: number; limit: number; } 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 c1bfc471bb..54cc6b3d25 100644 --- a/src/test/e2e/api/admin/event-search.e2e.test.ts +++ b/src/test/e2e/api/admin/event-search.e2e.test.ts @@ -575,3 +575,82 @@ test('should show user creation events for admins', async () => { total: 2, }); }); + +test('should filter events by environment', async () => { + await eventService.storeEvent({ + type: FEATURE_CREATED, + project: 'default', + environment: 'production', + createdBy: 'test-user', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + await eventService.storeEvent({ + type: FEATURE_CREATED, + project: 'default', + environment: 'staging', + createdBy: 'test-user', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + const { body } = await searchEvents({ environment: 'IS:production' }); + + expect(body).toMatchObject({ + events: [ + { + type: 'feature-created', + environment: 'production', + }, + ], + total: 1, + }); +}); + +test('should filter events by environment using IS_ANY_OF', async () => { + await eventService.storeEvent({ + type: FEATURE_CREATED, + project: 'default', + environment: 'production', + createdBy: 'test-user', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + await eventService.storeEvent({ + type: FEATURE_CREATED, + project: 'default', + environment: 'staging', + createdBy: 'test-user', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + await eventService.storeEvent({ + type: FEATURE_CREATED, + project: 'default', + environment: 'development', + createdBy: 'test-user', + createdByUserId: TEST_USER_ID, + ip: '127.0.0.1', + }); + + const { body } = await searchEvents({ + environment: 'IS_ANY_OF:production,staging', + }); + + expect(body).toMatchObject({ + events: [ + { + type: 'feature-created', + environment: 'staging', + }, + { + type: 'feature-created', + environment: 'production', + }, + ], + total: 2, + }); +});