1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00

feat: now it is possible to search events by group id (#10275)

Now when you put group ID as query param, it will filter based on
transaction id.

I am not sure if its best naming, whether it should be groupId or
transactionId, I will leave as group for now, but its simple change
later.


![image](https://github.com/user-attachments/assets/e0caaf57-f93f-40ee-a332-d3aed249c4ca)
This commit is contained in:
Jaanus Sellin 2025-07-02 10:16:13 +03:00 committed by GitHub
parent a251a9808a
commit 7e85de8f65
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 157 additions and 0 deletions

View File

@ -73,6 +73,7 @@ export const useEventLogSearch = (
type: FilterItemParam,
environment: FilterItemParam,
id: StringParam,
groupId: StringParam,
...extraParameters(logType),
};

View File

@ -222,6 +222,11 @@ export default class EventService {
if (parsed) queryParams.push(parsed);
}
if (params.groupId) {
const parsed = parseSearchOperatorValue('group_id', params.groupId);
if (parsed) queryParams.push(parsed);
}
['project', 'type', 'environment', 'id'].forEach((field) => {
if (params[field]) {
const parsed = parseSearchOperatorValue(field, params[field]);

View File

@ -22,6 +22,17 @@ export const eventSearchQueryParameters = [
'Filter by event ID using supported operators: IS, IS_ANY_OF.',
in: 'query',
},
{
name: 'groupId',
schema: {
type: 'string',
example: 'IS:123',
pattern: '^(IS|IS_ANY_OF):(.*?)(,([0-9]+))*$',
},
description:
'Filter by group ID using supported operators: IS, IS_ANY_OF.',
in: 'query',
},
{
name: 'feature',
schema: {

View File

@ -7,6 +7,7 @@ import type { IQueryParam } from '../../features/feature-toggle/types/feature-to
export interface IEventSearchParams {
id?: string;
groupId?: string;
project?: string;
query?: string;
feature?: string;

View File

@ -16,6 +16,8 @@ import {
import { createEventsService } from '../../../../lib/features/index.js';
import { createTestConfig } from '../../../config/test-config.js';
import { FEATURE_CREATED, USER_CREATED } from '../../../../lib/events/index.js';
import { withTransactional } from '../../../../lib/db/transaction.js';
import { EventStore } from '../../../../lib/features/events/event-store.js';
let app: IUnleashTest;
let db: ITestDb;
@ -718,3 +720,140 @@ test('should filter events by multiple IDs using IS_ANY_OF', async () => {
);
expect(returnedIds).not.toContain(feature2Event.id);
});
test('should filter events by group ID', async () => {
const eventStoreService = withTransactional(
(db) => new EventStore(db, getLogger),
db.rawDatabase,
);
const events = [
{
type: FEATURE_CREATED,
project: 'default',
data: { name: 'feature1' },
createdBy: 'test-user',
createdByUserId: TEST_USER_ID,
ip: '127.0.0.1',
},
{
type: FEATURE_CREATED,
project: 'default',
data: { name: 'feature2' },
createdBy: 'test-user',
createdByUserId: TEST_USER_ID,
ip: '127.0.0.1',
},
];
await eventStoreService.transactional(
async (transactionalEventStore) => {
await transactionalEventStore.batchStore(events);
},
{ type: 'transaction', id: '1' },
);
await eventService.storeEvent({
type: FEATURE_CREATED,
project: 'default',
data: { name: 'feature3' },
createdBy: 'test-user',
createdByUserId: TEST_USER_ID,
ip: '127.0.0.1',
});
const { body } = await searchEvents({ groupId: 'IS:1' });
expect(body.total).toBe(2);
expect(body.events).toHaveLength(2);
expect(body.events.every((e: any) => e.groupId === '1')).toBe(true);
});
test('should filter events by multiple group IDs using IS_ANY_OF', async () => {
const eventStoreService = withTransactional(
(db) => new EventStore(db, getLogger),
db.rawDatabase,
);
const event1 = {
type: FEATURE_CREATED,
project: 'default',
data: { name: 'feature1' },
createdBy: 'test-user',
createdByUserId: TEST_USER_ID,
ip: '127.0.0.1',
};
const event2 = {
type: FEATURE_CREATED,
project: 'default',
data: { name: 'feature2' },
createdBy: 'test-user',
createdByUserId: TEST_USER_ID,
ip: '127.0.0.1',
};
const event3 = {
type: FEATURE_CREATED,
project: 'default',
data: { name: 'feature3' },
createdBy: 'test-user',
createdByUserId: TEST_USER_ID,
ip: '127.0.0.1',
};
await eventStoreService.transactional(
async (transactionalEventStore) => {
await transactionalEventStore.store(event1);
},
{ type: 'transaction', id: '10' },
);
await eventStoreService.transactional(
async (transactionalEventStore) => {
await transactionalEventStore.store(event2);
},
{ type: 'transaction', id: '20' },
);
await eventStoreService.transactional(
async (transactionalEventStore) => {
await transactionalEventStore.store(event3);
},
{ type: 'transaction', id: '30' },
);
const { body } = await searchEvents({ groupId: 'IS_ANY_OF:10,30' });
expect(body.total).toBe(2);
expect(body.events).toHaveLength(2);
const returnedGroupIds = body.events.map((e: any) => e.groupId);
expect(returnedGroupIds).toContain('10');
expect(returnedGroupIds).toContain('30');
expect(returnedGroupIds).not.toContain('20');
});
test('Should return empty result when filtering by non-existent group ID', async () => {
const eventStoreService = withTransactional(
(db) => new EventStore(db, getLogger),
db.rawDatabase,
);
await eventStoreService.transactional(
async (transactionalEventStore) => {
await transactionalEventStore.store({
type: FEATURE_CREATED,
project: 'default',
data: { name: 'feature1' },
createdBy: 'test-user',
createdByUserId: TEST_USER_ID,
ip: '127.0.0.1',
});
},
{ type: 'transaction', id: '1' },
);
const { body } = await searchEvents({ groupId: 'IS:999' });
expect(body.total).toBe(0);
expect(body.events).toHaveLength(0);
});