mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
feat: support event id in search (#10176)
We added `id` as one of the query parameters for searching events.
This commit is contained in:
parent
63a354ab6f
commit
40840c98cf
@ -222,7 +222,7 @@ export default class EventService {
|
|||||||
if (parsed) queryParams.push(parsed);
|
if (parsed) queryParams.push(parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
['project', 'type', 'environment'].forEach((field) => {
|
['project', 'type', 'environment', 'id'].forEach((field) => {
|
||||||
if (params[field]) {
|
if (params[field]) {
|
||||||
const parsed = parseSearchOperatorValue(field, params[field]);
|
const parsed = parseSearchOperatorValue(field, params[field]);
|
||||||
if (parsed) queryParams.push(parsed);
|
if (parsed) queryParams.push(parsed);
|
||||||
|
@ -11,6 +11,17 @@ export const eventSearchQueryParameters = [
|
|||||||
'Find events by a free-text search query. The query will be matched against the event data payload (if any).',
|
'Find events by a free-text search query. The query will be matched against the event data payload (if any).',
|
||||||
in: 'query',
|
in: 'query',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
schema: {
|
||||||
|
type: 'string',
|
||||||
|
example: 'IS:123',
|
||||||
|
pattern: '^(IS|IS_ANY_OF):(.*?)(,([0-9]+))*$',
|
||||||
|
},
|
||||||
|
description:
|
||||||
|
'Filter by event ID using supported operators: IS, IS_ANY_OF.',
|
||||||
|
in: 'query',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'feature',
|
name: 'feature',
|
||||||
schema: {
|
schema: {
|
||||||
|
@ -6,6 +6,7 @@ import type { IQueryOperations } from '../../features/events/event-store.js';
|
|||||||
import type { IQueryParam } from '../../features/feature-toggle/types/feature-toggle-strategies-store-type.js';
|
import type { IQueryParam } from '../../features/feature-toggle/types/feature-toggle-strategies-store-type.js';
|
||||||
|
|
||||||
export interface IEventSearchParams {
|
export interface IEventSearchParams {
|
||||||
|
id?: string;
|
||||||
project?: string;
|
project?: string;
|
||||||
query?: string;
|
query?: string;
|
||||||
feature?: string;
|
feature?: string;
|
||||||
|
@ -618,3 +618,103 @@ test('should filter events by environment using IS_ANY_OF', async () => {
|
|||||||
total: 2,
|
total: 2,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should filter events by ID', async () => {
|
||||||
|
await eventService.storeEvent({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
project: 'default',
|
||||||
|
data: { name: 'feature1' },
|
||||||
|
createdBy: 'test-user',
|
||||||
|
createdByUserId: TEST_USER_ID,
|
||||||
|
ip: '127.0.0.1',
|
||||||
|
});
|
||||||
|
|
||||||
|
await eventService.storeEvent({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
project: 'default',
|
||||||
|
data: { name: 'feature2' },
|
||||||
|
createdBy: 'test-user',
|
||||||
|
createdByUserId: TEST_USER_ID,
|
||||||
|
ip: '127.0.0.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: allEventsResponse } = await searchEvents({});
|
||||||
|
const targetEvent = allEventsResponse.events.find(
|
||||||
|
(e: any) => e.data.name === 'feature2',
|
||||||
|
);
|
||||||
|
|
||||||
|
const { body } = await searchEvents({ id: `IS:${targetEvent.id}` });
|
||||||
|
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
id: targetEvent.id,
|
||||||
|
type: 'feature-created',
|
||||||
|
data: { name: 'feature2' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
total: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should filter events by multiple IDs using IS_ANY_OF', async () => {
|
||||||
|
await eventService.storeEvent({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
project: 'default',
|
||||||
|
data: { name: 'feature1' },
|
||||||
|
createdBy: 'test-user',
|
||||||
|
createdByUserId: TEST_USER_ID,
|
||||||
|
ip: '127.0.0.1',
|
||||||
|
});
|
||||||
|
|
||||||
|
await eventService.storeEvent({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
project: 'default',
|
||||||
|
data: { name: 'feature2' },
|
||||||
|
createdBy: 'test-user',
|
||||||
|
createdByUserId: TEST_USER_ID,
|
||||||
|
ip: '127.0.0.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: allEventsResponse } = await searchEvents({});
|
||||||
|
const targetEvent1 = allEventsResponse.events.find(
|
||||||
|
(e: any) => e.data.name === 'feature1',
|
||||||
|
);
|
||||||
|
const targetEvent3 = allEventsResponse.events.find(
|
||||||
|
(e: any) => e.data.name === 'feature3',
|
||||||
|
);
|
||||||
|
|
||||||
|
const { body } = await searchEvents({
|
||||||
|
id: `IS_ANY_OF:${targetEvent1.id},${targetEvent3.id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(body.total).toBe(2);
|
||||||
|
expect(body.events).toHaveLength(2);
|
||||||
|
|
||||||
|
const returnedIds = body.events.map((e: any) => e.id);
|
||||||
|
expect(returnedIds).toContain(targetEvent1.id);
|
||||||
|
expect(returnedIds).toContain(targetEvent3.id);
|
||||||
|
|
||||||
|
const feature2Event = allEventsResponse.events.find(
|
||||||
|
(e: any) => e.data.name === 'feature2',
|
||||||
|
);
|
||||||
|
expect(returnedIds).not.toContain(feature2Event.id);
|
||||||
|
});
|
||||||
|
@ -338,3 +338,137 @@ test('getMaxRevisionId should exclude FEATURE_CREATED and FEATURE_TAGGED events'
|
|||||||
expect(updatedEvent!.id).toBeGreaterThan(taggedEvent!.id);
|
expect(updatedEvent!.id).toBeGreaterThan(taggedEvent!.id);
|
||||||
expect(segmentEvent!.id).toBeGreaterThan(updatedEvent!.id);
|
expect(segmentEvent!.id).toBeGreaterThan(updatedEvent!.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Should filter events by ID using IS operator', async () => {
|
||||||
|
const event1 = {
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
createdBy: 'test-user',
|
||||||
|
createdByUserId: TEST_USER_ID,
|
||||||
|
ip: '127.0.0.1',
|
||||||
|
data: { name: 'feature1' },
|
||||||
|
};
|
||||||
|
const event2 = {
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
createdBy: 'test-user',
|
||||||
|
createdByUserId: TEST_USER_ID,
|
||||||
|
ip: '127.0.0.1',
|
||||||
|
data: { name: 'feature2' },
|
||||||
|
};
|
||||||
|
const event3 = {
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
createdBy: 'test-user',
|
||||||
|
createdByUserId: TEST_USER_ID,
|
||||||
|
ip: '127.0.0.1',
|
||||||
|
data: { name: 'feature3' },
|
||||||
|
};
|
||||||
|
|
||||||
|
await eventStore.store(event1);
|
||||||
|
await eventStore.store(event2);
|
||||||
|
await eventStore.store(event3);
|
||||||
|
|
||||||
|
const allEvents = await eventStore.getAll();
|
||||||
|
const targetEvent = allEvents.find((e) => e.data.name === 'feature2');
|
||||||
|
|
||||||
|
const filteredEvents = await eventStore.searchEvents(
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
limit: 10,
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
operator: 'IS',
|
||||||
|
values: [targetEvent!.id.toString()],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(filteredEvents).toHaveLength(1);
|
||||||
|
expect(filteredEvents[0].id).toBe(targetEvent!.id);
|
||||||
|
expect(filteredEvents[0].data.name).toBe('feature2');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should filter events by ID using IS_ANY_OF operator', async () => {
|
||||||
|
const event1 = {
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
createdBy: 'test-user',
|
||||||
|
createdByUserId: TEST_USER_ID,
|
||||||
|
ip: '127.0.0.1',
|
||||||
|
data: { name: 'feature1' },
|
||||||
|
};
|
||||||
|
const event2 = {
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
createdBy: 'test-user',
|
||||||
|
createdByUserId: TEST_USER_ID,
|
||||||
|
ip: '127.0.0.1',
|
||||||
|
data: { name: 'feature2' },
|
||||||
|
};
|
||||||
|
const event3 = {
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
createdBy: 'test-user',
|
||||||
|
createdByUserId: TEST_USER_ID,
|
||||||
|
ip: '127.0.0.1',
|
||||||
|
data: { name: 'feature3' },
|
||||||
|
};
|
||||||
|
|
||||||
|
await eventStore.store(event1);
|
||||||
|
await eventStore.store(event2);
|
||||||
|
await eventStore.store(event3);
|
||||||
|
|
||||||
|
const allEvents = await eventStore.getAll();
|
||||||
|
const targetEvent1 = allEvents.find((e) => e.data.name === 'feature1');
|
||||||
|
const targetEvent3 = allEvents.find((e) => e.data.name === 'feature3');
|
||||||
|
|
||||||
|
const filteredEvents = await eventStore.searchEvents(
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
limit: 10,
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
operator: 'IS_ANY_OF',
|
||||||
|
values: [
|
||||||
|
targetEvent1!.id.toString(),
|
||||||
|
targetEvent3!.id.toString(),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(filteredEvents).toHaveLength(2);
|
||||||
|
const eventIds = filteredEvents.map((e) => e.id);
|
||||||
|
expect(eventIds).toContain(targetEvent1!.id);
|
||||||
|
expect(eventIds).toContain(targetEvent3!.id);
|
||||||
|
expect(eventIds).not.toContain(
|
||||||
|
allEvents.find((e) => e.data.name === 'feature2')!.id,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should return empty result when filtering by non-existent ID', async () => {
|
||||||
|
const event = {
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
createdBy: 'test-user',
|
||||||
|
createdByUserId: TEST_USER_ID,
|
||||||
|
ip: '127.0.0.1',
|
||||||
|
data: { name: 'feature1' },
|
||||||
|
};
|
||||||
|
|
||||||
|
await eventStore.store(event);
|
||||||
|
|
||||||
|
const filteredEvents = await eventStore.searchEvents(
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
limit: 10,
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
operator: 'IS',
|
||||||
|
values: ['999999'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(filteredEvents).toHaveLength(0);
|
||||||
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user