mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: event search on new endpoint, first test (#7739)
Changed the url of event search to search/events to align with search/features. With that created a search controller to keep all searches under there. Added first test.
This commit is contained in:
parent
993d87516d
commit
57a8b9da79
101
src/lib/features/events/event-search-controller.ts
Normal file
101
src/lib/features/events/event-search-controller.ts
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import type { Response } from 'express';
|
||||||
|
import type { IUnleashConfig } from '../../types/option';
|
||||||
|
import type { IUnleashServices } from '../../types/services';
|
||||||
|
import type EventService from '../../features/events/event-service';
|
||||||
|
import { NONE } from '../../types/permissions';
|
||||||
|
import type { OpenApiService } from '../../services/openapi-service';
|
||||||
|
import { createResponseSchema } from '../../openapi/util/create-response-schema';
|
||||||
|
import { serializeDates } from '../../../lib/types/serialize-dates';
|
||||||
|
import type { IFlagResolver } from '../../types/experimental';
|
||||||
|
import {
|
||||||
|
type EventSearchQueryParameters,
|
||||||
|
eventSearchQueryParameters,
|
||||||
|
} from '../../openapi/spec/event-search-query-parameters';
|
||||||
|
import {
|
||||||
|
type EventSearchResponseSchema,
|
||||||
|
eventSearchResponseSchema,
|
||||||
|
} from '../../openapi';
|
||||||
|
import { normalizeQueryParams } from '../../features/feature-search/search-utils';
|
||||||
|
import Controller from '../../routes/controller';
|
||||||
|
import type { IAuthRequest } from '../../server-impl';
|
||||||
|
import type { IEvent } from '../../types';
|
||||||
|
import { anonymiseKeys } from '../../util';
|
||||||
|
|
||||||
|
const ANON_KEYS = ['email', 'username', 'createdBy'];
|
||||||
|
const version = 1 as const;
|
||||||
|
export default class EventSearchController extends Controller {
|
||||||
|
private eventService: EventService;
|
||||||
|
|
||||||
|
private flagResolver: IFlagResolver;
|
||||||
|
|
||||||
|
private openApiService: OpenApiService;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
config: IUnleashConfig,
|
||||||
|
{
|
||||||
|
eventService,
|
||||||
|
openApiService,
|
||||||
|
}: Pick<IUnleashServices, 'eventService' | 'openApiService'>,
|
||||||
|
) {
|
||||||
|
super(config);
|
||||||
|
this.eventService = eventService;
|
||||||
|
this.flagResolver = config.flagResolver;
|
||||||
|
this.openApiService = openApiService;
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
method: 'get',
|
||||||
|
path: '',
|
||||||
|
handler: this.searchEvents,
|
||||||
|
permission: NONE,
|
||||||
|
middleware: [
|
||||||
|
openApiService.validPath({
|
||||||
|
operationId: 'searchEvents',
|
||||||
|
tags: ['Events'],
|
||||||
|
summary: 'Search for events',
|
||||||
|
description:
|
||||||
|
'Allows searching for events matching the search criteria in the request body. This operation is deprecated. You should perform a GET request to the same endpoint with your query encoded as query parameters instead.',
|
||||||
|
parameters: [...eventSearchQueryParameters],
|
||||||
|
responses: {
|
||||||
|
200: createResponseSchema('eventSearchResponseSchema'),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchEvents(
|
||||||
|
req: IAuthRequest<any, any, any, EventSearchQueryParameters>,
|
||||||
|
res: Response<EventSearchResponseSchema>,
|
||||||
|
): Promise<void> {
|
||||||
|
const { normalizedLimit, normalizedOffset } = normalizeQueryParams(
|
||||||
|
req.query,
|
||||||
|
{
|
||||||
|
limitDefault: 50,
|
||||||
|
maxLimit: 1000,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { events, totalEvents } = await this.eventService.searchEvents({
|
||||||
|
...req.query,
|
||||||
|
offset: normalizedOffset,
|
||||||
|
limit: normalizedLimit,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.openApiService.respondWithValidation(
|
||||||
|
200,
|
||||||
|
res,
|
||||||
|
eventSearchResponseSchema.$id,
|
||||||
|
serializeDates({
|
||||||
|
events: serializeDates(this.maybeAnonymiseEvents(events)),
|
||||||
|
total: totalEvents,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
maybeAnonymiseEvents(events: IEvent[]): IEvent[] {
|
||||||
|
if (this.flagResolver.isEnabled('anonymiseEventLog')) {
|
||||||
|
return anonymiseKeys(events, ANON_KEYS);
|
||||||
|
}
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,16 @@
|
|||||||
import type { IUnleashConfig } from '../../types/option';
|
import type { IUnleashConfig } from '../../types/option';
|
||||||
import type { IFeatureTagStore, IUnleashStores } from '../../types/stores';
|
import type { IFeatureTagStore, IUnleashStores } from '../../types/stores';
|
||||||
import type { Logger } from '../../logger';
|
import type { Logger } from '../../logger';
|
||||||
import type { IEventStore } from '../../types/stores/event-store';
|
import type {
|
||||||
|
IEventSearchParams,
|
||||||
|
IEventStore,
|
||||||
|
} from '../../types/stores/event-store';
|
||||||
import type { IBaseEvent, IEventList } from '../../types/events';
|
import type { IBaseEvent, IEventList } from '../../types/events';
|
||||||
import type { DeprecatedSearchEventsSchema } from '../../openapi/spec/deprecated-search-events-schema';
|
import type { DeprecatedSearchEventsSchema } from '../../openapi/spec/deprecated-search-events-schema';
|
||||||
import type EventEmitter from 'events';
|
import type EventEmitter from 'events';
|
||||||
import type { IApiUser, ITag, IUser } from '../../types';
|
import type { IApiUser, ITag, IUser } from '../../types';
|
||||||
import { ApiTokenType } from '../../types/models/api-token';
|
import { ApiTokenType } from '../../types/models/api-token';
|
||||||
import { EVENTS_CREATED_BY_PROCESSED } from '../../metric-events';
|
import { EVENTS_CREATED_BY_PROCESSED } from '../../metric-events';
|
||||||
import type { EventSearchQueryParameters } from '../../openapi/spec/event-search-query-parameters';
|
|
||||||
import type { IQueryParam } from '../feature-toggle/types/feature-toggle-strategies-store-type';
|
import type { IQueryParam } from '../feature-toggle/types/feature-toggle-strategies-store-type';
|
||||||
import { parseSearchOperatorValue } from '../feature-search/search-utils';
|
import { parseSearchOperatorValue } from '../feature-search/search-utils';
|
||||||
|
|
||||||
@ -55,9 +57,7 @@ export default class EventService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async searchEvents(
|
async searchEvents(search: IEventSearchParams): Promise<IEventList> {
|
||||||
search: EventSearchQueryParameters,
|
|
||||||
): Promise<IEventList> {
|
|
||||||
const queryParams = this.convertToDbParams(search);
|
const queryParams = this.convertToDbParams(search);
|
||||||
const totalEvents = await this.eventStore.searchEventsCount(
|
const totalEvents = await this.eventStore.searchEventsCount(
|
||||||
{
|
{
|
||||||
@ -143,7 +143,7 @@ export default class EventService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
convertToDbParams = (params: EventSearchQueryParameters): IQueryParam[] => {
|
convertToDbParams = (params: IEventSearchParams): IQueryParam[] => {
|
||||||
const queryParams: IQueryParam[] = [];
|
const queryParams: IQueryParam[] = [];
|
||||||
|
|
||||||
if (params.createdAtFrom) {
|
if (params.createdAtFrom) {
|
||||||
|
@ -7,7 +7,10 @@ import {
|
|||||||
SEGMENT_UPDATED,
|
SEGMENT_UPDATED,
|
||||||
} from '../../types/events';
|
} from '../../types/events';
|
||||||
import type { Logger, LogProvider } from '../../logger';
|
import type { Logger, LogProvider } from '../../logger';
|
||||||
import type { IEventStore } from '../../types/stores/event-store';
|
import type {
|
||||||
|
IEventSearchParams,
|
||||||
|
IEventStore,
|
||||||
|
} from '../../types/stores/event-store';
|
||||||
import type { ITag } from '../../types/model';
|
import type { ITag } from '../../types/model';
|
||||||
import { sharedEventEmitter } from '../../util/anyEventEmitter';
|
import { sharedEventEmitter } from '../../util/anyEventEmitter';
|
||||||
import type { Db } from '../../db/db';
|
import type { Db } from '../../db/db';
|
||||||
@ -87,12 +90,6 @@ export interface IEventTable {
|
|||||||
tags: ITag[];
|
tags: ITag[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IEventSearchParams {
|
|
||||||
query: string | undefined;
|
|
||||||
offset: string | undefined;
|
|
||||||
limit: string | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TABLE = 'events';
|
const TABLE = 'events';
|
||||||
|
|
||||||
class EventStore implements IEventStore {
|
class EventStore implements IEventStore {
|
||||||
|
@ -24,8 +24,6 @@ import {
|
|||||||
import { normalizeQueryParams } from './search-utils';
|
import { normalizeQueryParams } from './search-utils';
|
||||||
import { anonymise } from '../../util';
|
import { anonymise } from '../../util';
|
||||||
|
|
||||||
const PATH = '/features';
|
|
||||||
|
|
||||||
type FeatureSearchServices = Pick<
|
type FeatureSearchServices = Pick<
|
||||||
IUnleashServices,
|
IUnleashServices,
|
||||||
'openApiService' | 'featureSearchService'
|
'openApiService' | 'featureSearchService'
|
||||||
@ -54,7 +52,7 @@ export default class FeatureSearchController extends Controller {
|
|||||||
|
|
||||||
this.route({
|
this.route({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
path: PATH,
|
path: '',
|
||||||
handler: this.searchFeatures,
|
handler: this.searchFeatures,
|
||||||
permission: NONE,
|
permission: NONE,
|
||||||
middleware: [
|
middleware: [
|
||||||
|
@ -8,7 +8,7 @@ export const eventSearchQueryParameters = [
|
|||||||
example: 'admin@example.com',
|
example: 'admin@example.com',
|
||||||
},
|
},
|
||||||
description:
|
description:
|
||||||
'Find events by a free-text search query. The query will be matched against the event type and 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',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -53,7 +53,7 @@ export const eventSearchQueryParameters = [
|
|||||||
pattern: '^(IS|IS_ANY_OF):(.*?)(,([a-zA-Z0-9_]+))*$',
|
pattern: '^(IS|IS_ANY_OF):(.*?)(,([a-zA-Z0-9_]+))*$',
|
||||||
},
|
},
|
||||||
description:
|
description:
|
||||||
'The ID of event creator to filter by. The creators can be specified with an operator. The supported operators are IS, IS_ANY_OF.',
|
'Filter by the ID of the event creator, using supported operators: IS, IS_ANY_OF.',
|
||||||
in: 'query',
|
in: 'query',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -83,6 +83,7 @@ export const eventSearchQueryParameters = [
|
|||||||
schema: {
|
schema: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
example: '50',
|
example: '50',
|
||||||
|
default: '0',
|
||||||
},
|
},
|
||||||
description:
|
description:
|
||||||
'The number of features to skip when returning a page. By default it is set to 0.',
|
'The number of features to skip when returning a page. By default it is set to 0.',
|
||||||
@ -93,9 +94,10 @@ export const eventSearchQueryParameters = [
|
|||||||
schema: {
|
schema: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
example: '50',
|
example: '50',
|
||||||
|
default: '50',
|
||||||
},
|
},
|
||||||
description:
|
description:
|
||||||
'The number of feature environments to return in a page. By default it is set to 50.',
|
'The number of feature environments to return in a page. By default it is set to 50. The maximum is 1000.',
|
||||||
in: 'query',
|
in: 'query',
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
@ -21,16 +21,6 @@ import { getStandardResponses } from '../../../lib/openapi/util/standard-respons
|
|||||||
import { createRequestSchema } from '../../openapi/util/create-request-schema';
|
import { createRequestSchema } from '../../openapi/util/create-request-schema';
|
||||||
import type { DeprecatedSearchEventsSchema } from '../../openapi/spec/deprecated-search-events-schema';
|
import type { DeprecatedSearchEventsSchema } from '../../openapi/spec/deprecated-search-events-schema';
|
||||||
import type { IFlagResolver } from '../../types/experimental';
|
import type { IFlagResolver } from '../../types/experimental';
|
||||||
import {
|
|
||||||
type EventSearchQueryParameters,
|
|
||||||
eventSearchQueryParameters,
|
|
||||||
} from '../../openapi/spec/event-search-query-parameters';
|
|
||||||
import type { IAuthRequest } from '../unleash-types';
|
|
||||||
import {
|
|
||||||
type EventSearchResponseSchema,
|
|
||||||
eventSearchResponseSchema,
|
|
||||||
} from '../../openapi';
|
|
||||||
import { normalizeQueryParams } from '../../features/feature-search/search-utils';
|
|
||||||
|
|
||||||
const ANON_KEYS = ['email', 'username', 'createdBy'];
|
const ANON_KEYS = ['email', 'username', 'createdBy'];
|
||||||
const version = 1 as const;
|
const version = 1 as const;
|
||||||
@ -125,26 +115,6 @@ export default class EventController extends Controller {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.route({
|
|
||||||
method: 'get',
|
|
||||||
path: '/search',
|
|
||||||
handler: this.searchEvents,
|
|
||||||
permission: NONE,
|
|
||||||
middleware: [
|
|
||||||
openApiService.validPath({
|
|
||||||
operationId: 'searchEvents',
|
|
||||||
tags: ['Events'],
|
|
||||||
summary: 'Search for events',
|
|
||||||
description:
|
|
||||||
'Allows searching for events matching the search criteria in the request body. This operation is deprecated. You should perform a GET request to the same endpoint with your query encoded as query parameters instead.',
|
|
||||||
parameters: [...eventSearchQueryParameters],
|
|
||||||
responses: {
|
|
||||||
200: createResponseSchema('eventSearchResponseSchema'),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
maybeAnonymiseEvents(events: IEvent[]): IEvent[] {
|
maybeAnonymiseEvents(events: IEvent[]): IEvent[] {
|
||||||
@ -227,33 +197,4 @@ export default class EventController extends Controller {
|
|||||||
response,
|
response,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async searchEvents(
|
|
||||||
req: IAuthRequest<any, any, any, EventSearchQueryParameters>,
|
|
||||||
res: Response<EventSearchResponseSchema>,
|
|
||||||
): Promise<void> {
|
|
||||||
const { normalizedLimit, normalizedOffset } = normalizeQueryParams(
|
|
||||||
req.query,
|
|
||||||
{
|
|
||||||
limitDefault: 50,
|
|
||||||
maxLimit: 1000,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const { events, totalEvents } = await this.eventService.searchEvents({
|
|
||||||
...req.body,
|
|
||||||
offset: normalizedOffset,
|
|
||||||
limit: normalizedLimit,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.openApiService.respondWithValidation(
|
|
||||||
200,
|
|
||||||
res,
|
|
||||||
eventSearchResponseSchema.$id,
|
|
||||||
serializeDates({
|
|
||||||
events: serializeDates(this.maybeAnonymiseEvents(events)),
|
|
||||||
total: totalEvents,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -32,9 +32,9 @@ import { createKnexTransactionStarter } from '../../db/transaction';
|
|||||||
import type { Db } from '../../db/db';
|
import type { Db } from '../../db/db';
|
||||||
import ExportImportController from '../../features/export-import-toggles/export-import-controller';
|
import ExportImportController from '../../features/export-import-toggles/export-import-controller';
|
||||||
import { SegmentsController } from '../../features/segment/segment-controller';
|
import { SegmentsController } from '../../features/segment/segment-controller';
|
||||||
import FeatureSearchController from '../../features/feature-search/feature-search-controller';
|
|
||||||
import { InactiveUsersController } from '../../users/inactive/inactive-users-controller';
|
import { InactiveUsersController } from '../../users/inactive/inactive-users-controller';
|
||||||
import { UiObservabilityController } from '../../features/ui-observability-controller/ui-observability-controller';
|
import { UiObservabilityController } from '../../features/ui-observability-controller/ui-observability-controller';
|
||||||
|
import { SearchApi } from './search';
|
||||||
|
|
||||||
export class AdminApi extends Controller {
|
export class AdminApi extends Controller {
|
||||||
constructor(config: IUnleashConfig, services: IUnleashServices, db: Db) {
|
constructor(config: IUnleashConfig, services: IUnleashServices, db: Db) {
|
||||||
@ -158,10 +158,7 @@ export class AdminApi extends Controller {
|
|||||||
new TelemetryController(config, services).router,
|
new TelemetryController(config, services).router,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.app.use(
|
this.app.use('/search', new SearchApi(config, services, db).router);
|
||||||
'/search',
|
|
||||||
new FeatureSearchController(config, services).router,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.app.use(
|
this.app.use(
|
||||||
'/record-ui-error',
|
'/record-ui-error',
|
||||||
|
24
src/lib/routes/admin-api/search/index.ts
Normal file
24
src/lib/routes/admin-api/search/index.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import EventSearchController from '../../../features/events/event-search-controller';
|
||||||
|
import FeatureSearchController from '../../../features/feature-search/feature-search-controller';
|
||||||
|
import type {
|
||||||
|
Db,
|
||||||
|
IUnleashConfig,
|
||||||
|
IUnleashServices,
|
||||||
|
} from '../../../server-impl';
|
||||||
|
import Controller from '../../controller';
|
||||||
|
|
||||||
|
export class SearchApi extends Controller {
|
||||||
|
constructor(config: IUnleashConfig, services: IUnleashServices, db: Db) {
|
||||||
|
super(config);
|
||||||
|
|
||||||
|
this.app.use(
|
||||||
|
'/features',
|
||||||
|
new FeatureSearchController(config, services).router,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.app.use(
|
||||||
|
'/events',
|
||||||
|
new EventSearchController(config, services).router,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -2,12 +2,20 @@ import type { IBaseEvent, IEvent } from '../events';
|
|||||||
import type { Store } from './store';
|
import type { Store } from './store';
|
||||||
import type { DeprecatedSearchEventsSchema } from '../../openapi';
|
import type { DeprecatedSearchEventsSchema } from '../../openapi';
|
||||||
import type EventEmitter from 'events';
|
import type EventEmitter from 'events';
|
||||||
import type {
|
import type { IQueryOperations } from '../../features/events/event-store';
|
||||||
IEventSearchParams,
|
|
||||||
IQueryOperations,
|
|
||||||
} from '../../features/events/event-store';
|
|
||||||
import type { IQueryParam } from '../../features/feature-toggle/types/feature-toggle-strategies-store-type';
|
import type { IQueryParam } from '../../features/feature-toggle/types/feature-toggle-strategies-store-type';
|
||||||
|
|
||||||
|
export interface IEventSearchParams {
|
||||||
|
project?: string;
|
||||||
|
query?: string;
|
||||||
|
createdAtFrom?: string;
|
||||||
|
createdAtTo?: string;
|
||||||
|
createdBy?: string;
|
||||||
|
type?: string;
|
||||||
|
offset: number;
|
||||||
|
limit: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IEventStore
|
export interface IEventStore
|
||||||
extends Store<IEvent, number>,
|
extends Store<IEvent, number>,
|
||||||
Pick<EventEmitter, 'on' | 'setMaxListeners' | 'emit' | 'off'> {
|
Pick<EventEmitter, 'on' | 'setMaxListeners' | 'emit' | 'off'> {
|
||||||
|
89
src/test/e2e/api/admin/event-search.e2e.test.ts
Normal file
89
src/test/e2e/api/admin/event-search.e2e.test.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import type { EventSearchQueryParameters } from '../../../../lib/openapi/spec/event-search-query-parameters';
|
||||||
|
import dbInit, { type ITestDb } from '../../helpers/database-init';
|
||||||
|
|
||||||
|
import { FEATURE_CREATED } from '../../../../lib/types';
|
||||||
|
import { EventService } from '../../../../lib/services';
|
||||||
|
import EventEmitter from 'events';
|
||||||
|
import getLogger from '../../../fixtures/no-logger';
|
||||||
|
import {
|
||||||
|
type IUnleashTest,
|
||||||
|
setupAppWithCustomConfig,
|
||||||
|
} from '../../helpers/test-helper';
|
||||||
|
|
||||||
|
let app: IUnleashTest;
|
||||||
|
let db: ITestDb;
|
||||||
|
let eventService: EventService;
|
||||||
|
const TEST_USER_ID = -9999;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
db = await dbInit('event_search', getLogger);
|
||||||
|
app = await setupAppWithCustomConfig(db.stores, {
|
||||||
|
experimental: {
|
||||||
|
flags: {
|
||||||
|
strictSchemaValidation: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
eventService = new EventService(db.stores, {
|
||||||
|
getLogger,
|
||||||
|
eventBus: new EventEmitter(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await app.destroy();
|
||||||
|
await db.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await db.stores.featureToggleStore.deleteAll();
|
||||||
|
await db.stores.segmentStore.deleteAll();
|
||||||
|
await db.stores.eventStore.deleteAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
const searchEvents = async (
|
||||||
|
queryParams: EventSearchQueryParameters,
|
||||||
|
expectedCode = 200,
|
||||||
|
) => {
|
||||||
|
const query = new URLSearchParams(queryParams as any).toString();
|
||||||
|
return app.request
|
||||||
|
.get(`/api/admin/search/events?${query}`)
|
||||||
|
.expect(expectedCode);
|
||||||
|
};
|
||||||
|
|
||||||
|
test('should search events by query', async () => {
|
||||||
|
await eventService.storeEvent({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
project: 'something-else',
|
||||||
|
data: { id: 'some-other-feature' },
|
||||||
|
tags: [],
|
||||||
|
createdBy: 'test-user',
|
||||||
|
createdByUserId: TEST_USER_ID,
|
||||||
|
ip: '127.0.0.1',
|
||||||
|
});
|
||||||
|
|
||||||
|
await eventService.storeEvent({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
project: 'something-else',
|
||||||
|
data: { id: 'my-other-feature' },
|
||||||
|
tags: [],
|
||||||
|
createdBy: 'test-user',
|
||||||
|
createdByUserId: TEST_USER_ID,
|
||||||
|
ip: '127.0.0.1',
|
||||||
|
});
|
||||||
|
|
||||||
|
const { body } = await searchEvents({ query: 'some-other-feature' });
|
||||||
|
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
type: 'feature-created',
|
||||||
|
data: {
|
||||||
|
id: 'some-other-feature',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
total: 1,
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user