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

feat: ability to search events by type with pagination

This commit is contained in:
Gastón Fournier 2025-06-26 12:11:12 +02:00
parent f588fbdfc4
commit a402ad8c66
No known key found for this signature in database
GPG Key ID: AF45428626E17A8E
3 changed files with 182 additions and 1 deletions

View File

@ -0,0 +1,110 @@
import EventStore from './event-store.js';
import getLogger from '../../../test/fixtures/no-logger.js';
import dbInit, {
type ITestDb,
} from '../../../test/e2e/helpers/database-init.js';
import {
USER_CREATED,
USER_DELETED,
USER_UPDATED,
} from '../../events/index.js';
import { EventSearchByType } from './event-search-by-type-read-model.js';
let db: ITestDb;
beforeAll(async () => {
getLogger.setMuteError(true);
db = await dbInit('event-search-by-type', getLogger);
});
afterAll(() => {
getLogger.setMuteError(false);
});
test('Can read search events by type', async () => {
const store = new EventStore(db.rawDatabase, getLogger);
const readModel = new EventSearchByType(db.rawDatabase, getLogger);
await store.store({
type: USER_CREATED,
createdBy: 'test',
ip: '127.0.0.1',
createdByUserId: 1,
});
const updatedEvents = await readModel.search({
types: [USER_UPDATED],
offset: 0,
limit: 10,
});
expect(updatedEvents).toBeTruthy();
expect(updatedEvents.length).toBe(0);
const createdEvents = await readModel.search({
types: [USER_CREATED],
offset: 0,
limit: 10,
});
expect(createdEvents).toBeTruthy();
expect(createdEvents.length).toBe(1);
});
test('Events by type are sorted and can be paginated', async () => {
const store = new EventStore(db.rawDatabase, getLogger);
const readModel = new EventSearchByType(db.rawDatabase, getLogger);
await store.store({
type: USER_DELETED,
data: { id: 1 },
createdBy: 'test',
ip: '127.0.0.1',
createdByUserId: 1,
});
await store.store({
type: USER_UPDATED,
data: { id: 2 },
createdBy: 'test',
ip: '127.0.0.1',
createdByUserId: 1,
});
await store.store({
type: USER_DELETED,
data: { id: 3 },
createdBy: 'test',
ip: '127.0.0.1',
createdByUserId: 1,
});
const allEvents = await readModel.search({
types: [USER_UPDATED, USER_DELETED],
offset: 0,
limit: 10,
});
expect(allEvents).toBeTruthy();
expect(allEvents.length).toBe(3);
const firstPage = await readModel.search({
types: [USER_UPDATED, USER_DELETED],
offset: 0,
limit: 2,
});
expect(firstPage).toBeTruthy();
expect(firstPage.length).toBe(2);
const secondPage = await readModel.search({
types: [USER_UPDATED, USER_DELETED],
offset: 2,
limit: 2,
});
expect(secondPage).toBeTruthy();
expect(secondPage.length).toBe(1);
expect(secondPage[0].type).toBe(USER_DELETED);
expect(secondPage[0].data.id).toBe(3);
const nonExistingPage = await readModel.search({
types: [USER_UPDATED, USER_DELETED],
offset: 4,
limit: 2,
});
expect(nonExistingPage).toBeTruthy();
expect(nonExistingPage.length).toBe(0);
});

View File

@ -0,0 +1,72 @@
import type { IEvent, IEventType } from '../../events/index.js';
import type { Logger, LogProvider } from '../../logger.js';
import type { Db } from '../../db/db.js';
import type { IEventTable } from './event-store.js';
const EVENT_COLUMNS = [
'id',
'type',
'created_by',
'created_at',
'created_by_user_id',
'data',
'pre_data',
'tags',
'feature_name',
'project',
'environment',
] as const;
const TABLE = 'events';
export interface EventSearchByTypeQueryParams {
types: string[];
offset: number;
limit: number;
order?: 'asc' | 'desc'; // asc by default
}
export interface EventSearchByTypeReadModel {
search(params: EventSearchByTypeQueryParams): Promise<IEvent[]>;
}
export class EventSearchByType implements EventSearchByTypeReadModel {
private db: Db;
private logger: Logger;
// a new DB has to be injected per transaction
constructor(db: Db, getLogger: LogProvider) {
this.db = db;
this.logger = getLogger('event-by-type');
}
async search(params: EventSearchByTypeQueryParams): Promise<IEvent[]> {
const query = this.db
.select(EVENT_COLUMNS)
.from(TABLE)
.whereIn('type', params.types)
.orderBy('id', params.order || 'asc')
.offset(params.offset)
.limit(params.limit);
const rows = await query;
return rows.map(this.rowToEvent);
}
rowToEvent(row: IEventTable): IEvent {
return {
id: row.id,
type: row.type as IEventType,
createdBy: row.created_by,
createdAt: row.created_at,
createdByUserId: row.created_by_user_id,
data: row.data,
preData: row.pre_data,
tags: row.tags || [],
featureName: row.feature_name,
project: row.project,
environment: row.environment,
};
}
}

View File

@ -14,7 +14,6 @@ export interface IEventSearchParams {
to?: string;
createdBy?: string;
type?: string;
types?: string[];
environment?: string;
offset: number;
limit: number;