mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +01:00
feat: add event search endpoint (#1893)
* feat: add event search endpoint * refactor: expand variable names * refactor: add table type to query builder * refactor: improve schema limit/offset types * refactor: describe searchEventsSchema fields
This commit is contained in:
parent
49095025ff
commit
a34c674971
@ -24,7 +24,7 @@ test('Trying to get events by name if db fails should yield empty list', async (
|
|||||||
client: 'pg',
|
client: 'pg',
|
||||||
});
|
});
|
||||||
const store = new EventStore(db, getLogger);
|
const store = new EventStore(db, getLogger);
|
||||||
const events = await store.getEventsFilterByType('application-created');
|
const events = await store.searchEvents({ type: 'application-created' });
|
||||||
expect(events).toBeTruthy();
|
expect(events).toBeTruthy();
|
||||||
expect(events.length).toBe(0);
|
expect(events.length).toBe(0);
|
||||||
});
|
});
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { DROP_FEATURES, IEvent, IBaseEvent } from '../types/events';
|
import { IEvent, IBaseEvent } from '../types/events';
|
||||||
import { LogProvider, Logger } from '../logger';
|
import { LogProvider, Logger } from '../logger';
|
||||||
import { IEventStore } from '../types/stores/event-store';
|
import { IEventStore } from '../types/stores/event-store';
|
||||||
import { ITag } from '../types/model';
|
import { ITag } from '../types/model';
|
||||||
|
import { SearchEventsSchema } from '../openapi/spec/search-events-schema';
|
||||||
|
|
||||||
const EVENT_COLUMNS = [
|
const EVENT_COLUMNS = [
|
||||||
'id',
|
'id',
|
||||||
@ -115,50 +116,44 @@ class EventStore extends EventEmitter implements IEventStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEventsFilterByType(name: string): Promise<IEvent[]> {
|
async searchEvents(search: SearchEventsSchema = {}): Promise<IEvent[]> {
|
||||||
try {
|
let query = this.db
|
||||||
const rows = await this.db
|
.select(EVENT_COLUMNS)
|
||||||
.select(EVENT_COLUMNS)
|
.from<IEventTable>(TABLE)
|
||||||
.from(TABLE)
|
.limit(search.limit ?? 100)
|
||||||
.limit(100)
|
.offset(search.offset ?? 0)
|
||||||
.where('type', name)
|
.orderBy('created_at', 'desc');
|
||||||
.andWhere(
|
|
||||||
'id',
|
|
||||||
'>=',
|
|
||||||
this.db
|
|
||||||
.select(this.db.raw('coalesce(max(id),0) as id'))
|
|
||||||
.from(TABLE)
|
|
||||||
.where({ type: DROP_FEATURES }),
|
|
||||||
)
|
|
||||||
.orderBy('created_at', 'desc');
|
|
||||||
return rows.map(this.rowToEvent);
|
|
||||||
} catch (err) {
|
|
||||||
this.logger.error(err);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getEventsFilterByProject(project: string): Promise<IEvent[]> {
|
if (search.type) {
|
||||||
try {
|
query = query.andWhere({
|
||||||
const rows = await this.db
|
type: search.type,
|
||||||
.select(EVENT_COLUMNS)
|
});
|
||||||
.from(TABLE)
|
}
|
||||||
.where({ project })
|
|
||||||
.orderBy('created_at', 'desc');
|
if (search.project) {
|
||||||
return rows.map(this.rowToEvent);
|
query = query.andWhere({
|
||||||
} catch (err) {
|
project: search.project,
|
||||||
return [];
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search.feature) {
|
||||||
|
query = query.andWhere({
|
||||||
|
feature_name: search.feature,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search.query) {
|
||||||
|
query = query.where((where) =>
|
||||||
|
where
|
||||||
|
.orWhereRaw('type::text ILIKE ?', `%${search.query}%`)
|
||||||
|
.orWhereRaw('created_by::text ILIKE ?', `%${search.query}%`)
|
||||||
|
.orWhereRaw('data::text ILIKE ?', `%${search.query}%`)
|
||||||
|
.orWhereRaw('pre_data::text ILIKE ?', `%${search.query}%`),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async getEventsForFeature(featureName: string): Promise<IEvent[]> {
|
|
||||||
try {
|
try {
|
||||||
const rows = await this.db
|
return (await query).map(this.rowToEvent);
|
||||||
.select(EVENT_COLUMNS)
|
|
||||||
.from(TABLE)
|
|
||||||
.where({ feature_name: featureName })
|
|
||||||
.orderBy('created_at', 'desc');
|
|
||||||
return rows.map(this.rowToEvent);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -104,6 +104,7 @@ import { groupSchema } from './spec/group-schema';
|
|||||||
import { groupsSchema } from './spec/groups-schema';
|
import { groupsSchema } from './spec/groups-schema';
|
||||||
import { groupUserModelSchema } from './spec/group-user-model-schema';
|
import { groupUserModelSchema } from './spec/group-user-model-schema';
|
||||||
import { usersGroupsBaseSchema } from './spec/users-groups-base-schema';
|
import { usersGroupsBaseSchema } from './spec/users-groups-base-schema';
|
||||||
|
import { searchEventsSchema } from './spec/search-events-schema';
|
||||||
|
|
||||||
// All schemas in `openapi/spec` should be listed here.
|
// All schemas in `openapi/spec` should be listed here.
|
||||||
export const schemas = {
|
export const schemas = {
|
||||||
@ -178,6 +179,7 @@ export const schemas = {
|
|||||||
resetPasswordSchema,
|
resetPasswordSchema,
|
||||||
roleSchema,
|
roleSchema,
|
||||||
sdkContextSchema,
|
sdkContextSchema,
|
||||||
|
searchEventsSchema,
|
||||||
segmentSchema,
|
segmentSchema,
|
||||||
setStrategySortOrderSchema,
|
setStrategySortOrderSchema,
|
||||||
sortOrderSchema,
|
sortOrderSchema,
|
||||||
|
@ -6,7 +6,7 @@ export const featureEventsSchema = {
|
|||||||
$id: '#/components/schemas/featureEventsSchema',
|
$id: '#/components/schemas/featureEventsSchema',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
required: ['toggleName', 'events'],
|
required: ['events'],
|
||||||
properties: {
|
properties: {
|
||||||
version: { type: 'number' },
|
version: { type: 'number' },
|
||||||
toggleName: {
|
toggleName: {
|
||||||
|
47
src/lib/openapi/spec/search-events-schema.ts
Normal file
47
src/lib/openapi/spec/search-events-schema.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
export const searchEventsSchema = {
|
||||||
|
$id: '#/components/schemas/searchEventsSchema',
|
||||||
|
type: 'object',
|
||||||
|
description: `
|
||||||
|
Search for events by type, project, feature, free-text query,
|
||||||
|
or a combination thereof. Pass an empty object to fetch all events.
|
||||||
|
`,
|
||||||
|
properties: {
|
||||||
|
type: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Find events by event type (case-sensitive).',
|
||||||
|
},
|
||||||
|
project: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Find events by project ID (case-sensitive).',
|
||||||
|
},
|
||||||
|
feature: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Find events by feature toggle name (case-sensitive).',
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
type: 'string',
|
||||||
|
description: `
|
||||||
|
Find events by a free-text search query.
|
||||||
|
The query will be matched against the event type,
|
||||||
|
the username or email that created the event (if any),
|
||||||
|
and the event data payload (if any).
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: 'integer',
|
||||||
|
minimum: 1,
|
||||||
|
maximum: 100,
|
||||||
|
default: 100,
|
||||||
|
},
|
||||||
|
offset: {
|
||||||
|
type: 'integer',
|
||||||
|
minimum: 0,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type SearchEventsSchema = FromSchema<typeof searchEventsSchema>;
|
@ -19,6 +19,8 @@ import {
|
|||||||
FeatureEventsSchema,
|
FeatureEventsSchema,
|
||||||
} from '../../../lib/openapi/spec/feature-events-schema';
|
} from '../../../lib/openapi/spec/feature-events-schema';
|
||||||
import { getStandardResponses } from '../../../lib/openapi/util/standard-responses';
|
import { getStandardResponses } from '../../../lib/openapi/util/standard-responses';
|
||||||
|
import { createRequestSchema } from '../../openapi/util/create-request-schema';
|
||||||
|
import { SearchEventsSchema } from '../../openapi/spec/search-events-schema';
|
||||||
|
|
||||||
const version = 1;
|
const version = 1;
|
||||||
export default class EventController extends Controller {
|
export default class EventController extends Controller {
|
||||||
@ -86,9 +88,24 @@ export default class EventController extends Controller {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
method: 'post',
|
||||||
|
path: '/search',
|
||||||
|
handler: this.searchEvents,
|
||||||
|
permission: NONE,
|
||||||
|
middleware: [
|
||||||
|
openApiService.validPath({
|
||||||
|
operationId: 'searchEvents',
|
||||||
|
tags: ['admin'],
|
||||||
|
requestBody: createRequestSchema('searchEventsSchema'),
|
||||||
|
responses: { 200: createResponseSchema('eventsSchema') },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fixEvents(events: IEvent[]): IEvent[] {
|
maybeAnonymiseEvents(events: IEvent[]): IEvent[] {
|
||||||
if (this.anonymise) {
|
if (this.anonymise) {
|
||||||
return events.map((e: IEvent) => ({
|
return events.map((e: IEvent) => ({
|
||||||
...e,
|
...e,
|
||||||
@ -105,15 +122,16 @@ export default class EventController extends Controller {
|
|||||||
const { project } = req.query;
|
const { project } = req.query;
|
||||||
let events: IEvent[];
|
let events: IEvent[];
|
||||||
if (project) {
|
if (project) {
|
||||||
events = await this.eventService.getEventsForProject(project);
|
events = await this.eventService.searchEvents({ project });
|
||||||
} else {
|
} else {
|
||||||
events = await this.eventService.getEvents();
|
events = await this.eventService.getEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
const response: EventsSchema = {
|
const response: EventsSchema = {
|
||||||
version,
|
version,
|
||||||
events: serializeDates(this.fixEvents(events)),
|
events: serializeDates(this.maybeAnonymiseEvents(events)),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.openApiService.respondWithValidation(
|
this.openApiService.respondWithValidation(
|
||||||
200,
|
200,
|
||||||
res,
|
res,
|
||||||
@ -126,13 +144,32 @@ export default class EventController extends Controller {
|
|||||||
req: Request<{ featureName: string }>,
|
req: Request<{ featureName: string }>,
|
||||||
res: Response<FeatureEventsSchema>,
|
res: Response<FeatureEventsSchema>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const toggleName = req.params.featureName;
|
const feature = req.params.featureName;
|
||||||
const events = await this.eventService.getEventsForToggle(toggleName);
|
const events = await this.eventService.searchEvents({ feature });
|
||||||
|
|
||||||
const response = {
|
const response = {
|
||||||
version,
|
version,
|
||||||
toggleName,
|
toggleName: feature,
|
||||||
events: serializeDates(this.fixEvents(events)),
|
events: serializeDates(this.maybeAnonymiseEvents(events)),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.openApiService.respondWithValidation(
|
||||||
|
200,
|
||||||
|
res,
|
||||||
|
featureEventsSchema.$id,
|
||||||
|
response,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchEvents(
|
||||||
|
req: Request<unknown, unknown, SearchEventsSchema>,
|
||||||
|
res: Response<EventsSchema>,
|
||||||
|
): Promise<void> {
|
||||||
|
const events = await this.eventService.searchEvents(req.body);
|
||||||
|
|
||||||
|
const response = {
|
||||||
|
version,
|
||||||
|
events: serializeDates(this.maybeAnonymiseEvents(events)),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.openApiService.respondWithValidation(
|
this.openApiService.respondWithValidation(
|
||||||
|
@ -3,6 +3,7 @@ import { IUnleashStores } from '../types/stores';
|
|||||||
import { Logger } from '../logger';
|
import { Logger } from '../logger';
|
||||||
import { IEventStore } from '../types/stores/event-store';
|
import { IEventStore } from '../types/stores/event-store';
|
||||||
import { IEvent } from '../types/events';
|
import { IEvent } from '../types/events';
|
||||||
|
import { SearchEventsSchema } from '../openapi/spec/search-events-schema';
|
||||||
|
|
||||||
export default class EventService {
|
export default class EventService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
@ -21,12 +22,8 @@ export default class EventService {
|
|||||||
return this.eventStore.getEvents();
|
return this.eventStore.getEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEventsForToggle(name: string): Promise<IEvent[]> {
|
async searchEvents(search: SearchEventsSchema): Promise<IEvent[]> {
|
||||||
return this.eventStore.getEventsForFeature(name);
|
return this.eventStore.searchEvents(search);
|
||||||
}
|
|
||||||
|
|
||||||
async getEventsForProject(project: string): Promise<IEvent[]> {
|
|
||||||
return this.eventStore.getEventsFilterByProject(project);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
import { IBaseEvent, IEvent } from '../events';
|
import { IBaseEvent, IEvent } from '../events';
|
||||||
import { Store } from './store';
|
import { Store } from './store';
|
||||||
|
import { SearchEventsSchema } from '../../openapi/spec/search-events-schema';
|
||||||
|
|
||||||
export interface IEventStore extends Store<IEvent, number>, EventEmitter {
|
export interface IEventStore extends Store<IEvent, number>, EventEmitter {
|
||||||
store(event: IBaseEvent): Promise<void>;
|
store(event: IBaseEvent): Promise<void>;
|
||||||
batchStore(events: IBaseEvent[]): Promise<void>;
|
batchStore(events: IBaseEvent[]): Promise<void>;
|
||||||
getEvents(): Promise<IEvent[]>;
|
getEvents(): Promise<IEvent[]>;
|
||||||
getEventsFilterByType(name: string): Promise<IEvent[]>;
|
searchEvents(search: SearchEventsSchema): Promise<IEvent[]>;
|
||||||
getEventsForFeature(featureName: string): Promise<IEvent[]>;
|
|
||||||
getEventsFilterByProject(project: string): Promise<IEvent[]>;
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { IUnleashTest, setupApp } from '../../helpers/test-helper';
|
import { IUnleashTest, setupApp } from '../../helpers/test-helper';
|
||||||
import dbInit, { ITestDb } from '../../helpers/database-init';
|
import dbInit, { ITestDb } from '../../helpers/database-init';
|
||||||
import getLogger from '../../../fixtures/no-logger';
|
import getLogger from '../../../fixtures/no-logger';
|
||||||
import { FEATURE_CREATED } from '../../../../lib/types/events';
|
import { FEATURE_CREATED, IBaseEvent } from '../../../../lib/types/events';
|
||||||
import { IEventStore } from '../../../../lib/types/stores/event-store';
|
import { IEventStore } from '../../../../lib/types/stores/event-store';
|
||||||
|
import { randomId } from '../../../../lib/util/random-id';
|
||||||
|
|
||||||
let app: IUnleashTest;
|
let app: IUnleashTest;
|
||||||
let db: ITestDb;
|
let db: ITestDb;
|
||||||
@ -14,6 +15,10 @@ beforeAll(async () => {
|
|||||||
eventStore = db.stores.eventStore;
|
eventStore = db.stores.eventStore;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await eventStore.deleteAll();
|
||||||
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await app.destroy();
|
await app.destroy();
|
||||||
await db.destroy();
|
await db.destroy();
|
||||||
@ -60,3 +65,61 @@ test('Can filter by project', async () => {
|
|||||||
expect(res.body.events[0].data.id).toEqual('feature');
|
expect(res.body.events[0].data.id).toEqual('feature');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('can search for events', async () => {
|
||||||
|
const events: IBaseEvent[] = [
|
||||||
|
{
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
project: randomId(),
|
||||||
|
data: { id: randomId() },
|
||||||
|
tags: [],
|
||||||
|
createdBy: randomId(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
project: randomId(),
|
||||||
|
data: { id: randomId() },
|
||||||
|
preData: { id: randomId() },
|
||||||
|
tags: [],
|
||||||
|
createdBy: randomId(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
events.map((event) => {
|
||||||
|
return eventStore.store(event);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await app.request
|
||||||
|
.post('/api/admin/events/search')
|
||||||
|
.send({})
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.events).toHaveLength(2);
|
||||||
|
});
|
||||||
|
await app.request
|
||||||
|
.post('/api/admin/events/search')
|
||||||
|
.send({ limit: 1, offset: 1 })
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.events).toHaveLength(1);
|
||||||
|
expect(res.body.events[0].data.id).toEqual(events[0].data.id);
|
||||||
|
});
|
||||||
|
await app.request
|
||||||
|
.post('/api/admin/events/search')
|
||||||
|
.send({ query: events[1].data.id })
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.events).toHaveLength(1);
|
||||||
|
expect(res.body.events[0].data.id).toEqual(events[1].data.id);
|
||||||
|
});
|
||||||
|
await app.request
|
||||||
|
.post('/api/admin/events/search')
|
||||||
|
.send({ query: events[1].preData.id })
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.events).toHaveLength(1);
|
||||||
|
expect(res.body.events[0].preData.id).toEqual(events[1].preData.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -1010,7 +1010,6 @@ Object {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"required": Array [
|
"required": Array [
|
||||||
"toggleName",
|
|
||||||
"events",
|
"events",
|
||||||
],
|
],
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -2218,6 +2217,47 @@ Object {
|
|||||||
],
|
],
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
|
"searchEventsSchema": Object {
|
||||||
|
"description": "
|
||||||
|
Search for events by type, project, feature, free-text query,
|
||||||
|
or a combination thereof. Pass an empty object to fetch all events.
|
||||||
|
",
|
||||||
|
"properties": Object {
|
||||||
|
"feature": Object {
|
||||||
|
"description": "Find events by feature toggle name (case-sensitive).",
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"limit": Object {
|
||||||
|
"default": 100,
|
||||||
|
"maximum": 100,
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "integer",
|
||||||
|
},
|
||||||
|
"offset": Object {
|
||||||
|
"default": 0,
|
||||||
|
"minimum": 0,
|
||||||
|
"type": "integer",
|
||||||
|
},
|
||||||
|
"project": Object {
|
||||||
|
"description": "Find events by project ID (case-sensitive).",
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"query": Object {
|
||||||
|
"description": "
|
||||||
|
Find events by a free-text search query.
|
||||||
|
The query will be matched against the event type,
|
||||||
|
the username or email that created the event (if any),
|
||||||
|
and the event data payload (if any).
|
||||||
|
",
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"type": Object {
|
||||||
|
"description": "Find events by event type (case-sensitive).",
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
"segmentSchema": Object {
|
"segmentSchema": Object {
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
@ -3736,6 +3776,37 @@ If the provided project does not exist, the list of events will be empty.",
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"/api/admin/events/search": Object {
|
||||||
|
"post": Object {
|
||||||
|
"operationId": "searchEvents",
|
||||||
|
"requestBody": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/searchEventsSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "searchEventsSchema",
|
||||||
|
"required": true,
|
||||||
|
},
|
||||||
|
"responses": Object {
|
||||||
|
"200": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/eventsSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "eventsSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tags": Array [
|
||||||
|
"admin",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
"/api/admin/events/{featureName}": Object {
|
"/api/admin/events/{featureName}": Object {
|
||||||
"get": Object {
|
"get": Object {
|
||||||
"description": "Returns all events related to the specified feature toggle. If the feature toggle does not exist, the list of events will be empty.",
|
"description": "Returns all events related to the specified feature toggle. If the feature toggle does not exist, the list of events will be empty.",
|
||||||
|
@ -32,9 +32,9 @@ test('Can create new setting', async () => {
|
|||||||
|
|
||||||
expect(actual).toStrictEqual(someData);
|
expect(actual).toStrictEqual(someData);
|
||||||
const { eventStore } = stores;
|
const { eventStore } = stores;
|
||||||
const createdEvents = await eventStore.getEventsFilterByType(
|
const createdEvents = await eventStore.searchEvents({
|
||||||
SETTING_CREATED,
|
type: SETTING_CREATED,
|
||||||
);
|
});
|
||||||
expect(createdEvents).toHaveLength(1);
|
expect(createdEvents).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -46,9 +46,9 @@ test('Can delete setting', async () => {
|
|||||||
const actual = await service.get('some-setting');
|
const actual = await service.get('some-setting');
|
||||||
expect(actual).toBeUndefined();
|
expect(actual).toBeUndefined();
|
||||||
const { eventStore } = stores;
|
const { eventStore } = stores;
|
||||||
const createdEvents = await eventStore.getEventsFilterByType(
|
const createdEvents = await eventStore.searchEvents({
|
||||||
SETTING_DELETED,
|
type: SETTING_DELETED,
|
||||||
);
|
});
|
||||||
expect(createdEvents).toHaveLength(1);
|
expect(createdEvents).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -61,8 +61,8 @@ test('Can update setting', async () => {
|
|||||||
{ ...someData, test: 'fun' },
|
{ ...someData, test: 'fun' },
|
||||||
'test-user',
|
'test-user',
|
||||||
);
|
);
|
||||||
const updatedEvents = await eventStore.getEventsFilterByType(
|
const updatedEvents = await eventStore.searchEvents({
|
||||||
SETTING_UPDATED,
|
type: SETTING_UPDATED,
|
||||||
);
|
});
|
||||||
expect(updatedEvents).toHaveLength(1);
|
expect(updatedEvents).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
@ -209,12 +209,12 @@ test('Should get all events of type', async () => {
|
|||||||
return eventStore.store(event);
|
return eventStore.store(event);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const featureCreatedEvents = await eventStore.getEventsFilterByType(
|
const featureCreatedEvents = await eventStore.searchEvents({
|
||||||
FEATURE_CREATED,
|
type: FEATURE_CREATED,
|
||||||
);
|
});
|
||||||
expect(featureCreatedEvents).toHaveLength(3);
|
expect(featureCreatedEvents).toHaveLength(3);
|
||||||
const featureDeletedEvents = await eventStore.getEventsFilterByType(
|
const featureDeletedEvents = await eventStore.searchEvents({
|
||||||
FEATURE_DELETED,
|
type: FEATURE_DELETED,
|
||||||
);
|
});
|
||||||
expect(featureDeletedEvents).toHaveLength(3);
|
expect(featureDeletedEvents).toHaveLength(3);
|
||||||
});
|
});
|
||||||
|
12
src/test/fixtures/fake-event-store.ts
vendored
12
src/test/fixtures/fake-event-store.ts
vendored
@ -11,10 +11,6 @@ class FakeEventStore extends EventEmitter implements IEventStore {
|
|||||||
this.events = [];
|
this.events = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEventsForFeature(featureName: string): Promise<IEvent[]> {
|
|
||||||
return this.events.filter((e) => e.featureName === featureName);
|
|
||||||
}
|
|
||||||
|
|
||||||
store(event: IEvent): Promise<void> {
|
store(event: IEvent): Promise<void> {
|
||||||
this.events.push(event);
|
this.events.push(event);
|
||||||
this.emit(event.type, event);
|
this.emit(event.type, event);
|
||||||
@ -58,12 +54,8 @@ class FakeEventStore extends EventEmitter implements IEventStore {
|
|||||||
return this.events;
|
return this.events;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEventsFilterByType(type: string): Promise<IEvent[]> {
|
async searchEvents(): Promise<IEvent[]> {
|
||||||
return this.events.filter((e) => e.type === type);
|
throw new Error('Method not implemented.');
|
||||||
}
|
|
||||||
|
|
||||||
async getEventsFilterByProject(project: string): Promise<IEvent[]> {
|
|
||||||
return this.events.filter((e) => e.project === project);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user