mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-27 01:19:00 +02:00
chore: prefer searchEvents over deprecated methods (#10031)
https://linear.app/unleash/issue/2-3577/prefer-the-new-searchevents-method-over-deprecated-methods We should favor our new `searchEvents` method over the other deprecated methods. This PR removes these deprecated methods, replaces them with the new `searchEvents` and `searchEventsCount` methods, and marks the old endpoints as deprecated. Follow-up to: https://github.com/Unleash/unleash/pull/10030
This commit is contained in:
parent
2879ce9dd6
commit
a419b8e098
@ -1,34 +0,0 @@
|
|||||||
/**
|
|
||||||
* Generated by Orval
|
|
||||||
* Do not edit manually.
|
|
||||||
* See `gen:api` script in package.json
|
|
||||||
*/
|
|
||||||
import type { DeprecatedSearchEventsSchemaType } from './deprecatedSearchEventsSchemaType.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
Search for events by type, project, feature, free-text query,
|
|
||||||
or a combination thereof. Pass an empty object to fetch all events.
|
|
||||||
|
|
||||||
*/
|
|
||||||
export interface DeprecatedSearchEventsSchema {
|
|
||||||
/** Find events by feature flag name (case-sensitive). */
|
|
||||||
feature?: string;
|
|
||||||
/**
|
|
||||||
* The maximum amount of events to return in the search result
|
|
||||||
* @minimum 1
|
|
||||||
* @maximum 100
|
|
||||||
*/
|
|
||||||
limit?: number;
|
|
||||||
/**
|
|
||||||
* Which event id to start listing from
|
|
||||||
* @minimum 0
|
|
||||||
*/
|
|
||||||
offset?: number;
|
|
||||||
/** Find events by project ID (case-sensitive). */
|
|
||||||
project?: string;
|
|
||||||
/** 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). */
|
|
||||||
query?: string;
|
|
||||||
/** Find events by event type (case-sensitive). */
|
|
||||||
type?: DeprecatedSearchEventsSchemaType;
|
|
||||||
}
|
|
@ -1,178 +0,0 @@
|
|||||||
/**
|
|
||||||
* Generated by Orval
|
|
||||||
* Do not edit manually.
|
|
||||||
* See `gen:api` script in package.json
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find events by event type (case-sensitive).
|
|
||||||
*/
|
|
||||||
export type DeprecatedSearchEventsSchemaType =
|
|
||||||
(typeof DeprecatedSearchEventsSchemaType)[keyof typeof DeprecatedSearchEventsSchemaType];
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
|
||||||
export const DeprecatedSearchEventsSchemaType = {
|
|
||||||
'application-created': 'application-created',
|
|
||||||
'feature-created': 'feature-created',
|
|
||||||
'feature-deleted': 'feature-deleted',
|
|
||||||
'feature-updated': 'feature-updated',
|
|
||||||
'feature-metadata-updated': 'feature-metadata-updated',
|
|
||||||
'feature-variants-updated': 'feature-variants-updated',
|
|
||||||
'feature-environment-variants-updated':
|
|
||||||
'feature-environment-variants-updated',
|
|
||||||
'feature-project-change': 'feature-project-change',
|
|
||||||
'feature-archived': 'feature-archived',
|
|
||||||
'feature-revived': 'feature-revived',
|
|
||||||
'feature-import': 'feature-import',
|
|
||||||
'feature-tagged': 'feature-tagged',
|
|
||||||
'feature-tag-import': 'feature-tag-import',
|
|
||||||
'feature-strategy-update': 'feature-strategy-update',
|
|
||||||
'feature-strategy-add': 'feature-strategy-add',
|
|
||||||
'feature-strategy-remove': 'feature-strategy-remove',
|
|
||||||
'feature-type-updated': 'feature-type-updated',
|
|
||||||
'feature-completed': 'feature-completed',
|
|
||||||
'feature-uncompleted': 'feature-uncompleted',
|
|
||||||
'feature-link-added': 'feature-link-added',
|
|
||||||
'feature-link-removed': 'feature-link-removed',
|
|
||||||
'feature-link-updated': 'feature-link-updated',
|
|
||||||
'strategy-order-changed': 'strategy-order-changed',
|
|
||||||
'drop-feature-tags': 'drop-feature-tags',
|
|
||||||
'feature-untagged': 'feature-untagged',
|
|
||||||
'feature-stale-on': 'feature-stale-on',
|
|
||||||
'feature-stale-off': 'feature-stale-off',
|
|
||||||
'drop-features': 'drop-features',
|
|
||||||
'feature-environment-enabled': 'feature-environment-enabled',
|
|
||||||
'feature-environment-disabled': 'feature-environment-disabled',
|
|
||||||
'strategy-created': 'strategy-created',
|
|
||||||
'strategy-deleted': 'strategy-deleted',
|
|
||||||
'strategy-deprecated': 'strategy-deprecated',
|
|
||||||
'strategy-reactivated': 'strategy-reactivated',
|
|
||||||
'strategy-updated': 'strategy-updated',
|
|
||||||
'strategy-import': 'strategy-import',
|
|
||||||
'drop-strategies': 'drop-strategies',
|
|
||||||
'context-field-created': 'context-field-created',
|
|
||||||
'context-field-updated': 'context-field-updated',
|
|
||||||
'context-field-deleted': 'context-field-deleted',
|
|
||||||
'project-access-added': 'project-access-added',
|
|
||||||
'project-access-user-roles-updated': 'project-access-user-roles-updated',
|
|
||||||
'project-access-group-roles-updated': 'project-access-group-roles-updated',
|
|
||||||
'project-access-user-roles-deleted': 'project-access-user-roles-deleted',
|
|
||||||
'project-access-group-roles-deleted': 'project-access-group-roles-deleted',
|
|
||||||
'project-access-updated': 'project-access-updated',
|
|
||||||
'project-created': 'project-created',
|
|
||||||
'project-updated': 'project-updated',
|
|
||||||
'project-deleted': 'project-deleted',
|
|
||||||
'project-archived': 'project-archived',
|
|
||||||
'project-revived': 'project-revived',
|
|
||||||
'project-import': 'project-import',
|
|
||||||
'project-user-added': 'project-user-added',
|
|
||||||
'project-user-removed': 'project-user-removed',
|
|
||||||
'project-user-role-changed': 'project-user-role-changed',
|
|
||||||
'project-group-role-changed': 'project-group-role-changed',
|
|
||||||
'project-group-added': 'project-group-added',
|
|
||||||
'project-group-removed': 'project-group-removed',
|
|
||||||
'role-created': 'role-created',
|
|
||||||
'role-updated': 'role-updated',
|
|
||||||
'role-deleted': 'role-deleted',
|
|
||||||
'drop-projects': 'drop-projects',
|
|
||||||
'tag-created': 'tag-created',
|
|
||||||
'tag-deleted': 'tag-deleted',
|
|
||||||
'tag-import': 'tag-import',
|
|
||||||
'drop-tags': 'drop-tags',
|
|
||||||
'tag-type-created': 'tag-type-created',
|
|
||||||
'tag-type-deleted': 'tag-type-deleted',
|
|
||||||
'tag-type-updated': 'tag-type-updated',
|
|
||||||
'tag-type-import': 'tag-type-import',
|
|
||||||
'drop-tag-types': 'drop-tag-types',
|
|
||||||
'addon-config-created': 'addon-config-created',
|
|
||||||
'addon-config-updated': 'addon-config-updated',
|
|
||||||
'addon-config-deleted': 'addon-config-deleted',
|
|
||||||
'db-pool-update': 'db-pool-update',
|
|
||||||
'user-created': 'user-created',
|
|
||||||
'user-updated': 'user-updated',
|
|
||||||
'user-deleted': 'user-deleted',
|
|
||||||
'drop-environments': 'drop-environments',
|
|
||||||
'environment-import': 'environment-import',
|
|
||||||
'environment-created': 'environment-created',
|
|
||||||
'environment-updated': 'environment-updated',
|
|
||||||
'environment-deleted': 'environment-deleted',
|
|
||||||
'segment-created': 'segment-created',
|
|
||||||
'segment-updated': 'segment-updated',
|
|
||||||
'segment-deleted': 'segment-deleted',
|
|
||||||
'group-created': 'group-created',
|
|
||||||
'group-updated': 'group-updated',
|
|
||||||
'group-deleted': 'group-deleted',
|
|
||||||
'group-user-added': 'group-user-added',
|
|
||||||
'group-user-removed': 'group-user-removed',
|
|
||||||
'setting-created': 'setting-created',
|
|
||||||
'setting-updated': 'setting-updated',
|
|
||||||
'setting-deleted': 'setting-deleted',
|
|
||||||
'client-metrics': 'client-metrics',
|
|
||||||
'client-register': 'client-register',
|
|
||||||
'pat-created': 'pat-created',
|
|
||||||
'pat-deleted': 'pat-deleted',
|
|
||||||
'public-signup-token-created': 'public-signup-token-created',
|
|
||||||
'public-signup-token-user-added': 'public-signup-token-user-added',
|
|
||||||
'public-signup-token-updated': 'public-signup-token-updated',
|
|
||||||
'change-request-created': 'change-request-created',
|
|
||||||
'change-request-discarded': 'change-request-discarded',
|
|
||||||
'change-added': 'change-added',
|
|
||||||
'change-discarded': 'change-discarded',
|
|
||||||
'change-edited': 'change-edited',
|
|
||||||
'change-request-rejected': 'change-request-rejected',
|
|
||||||
'change-request-approved': 'change-request-approved',
|
|
||||||
'change-request-approval-added': 'change-request-approval-added',
|
|
||||||
'change-request-cancelled': 'change-request-cancelled',
|
|
||||||
'change-request-sent-to-review': 'change-request-sent-to-review',
|
|
||||||
'change-request-schedule-suspended': 'change-request-schedule-suspended',
|
|
||||||
'change-request-applied': 'change-request-applied',
|
|
||||||
'change-request-scheduled': 'change-request-scheduled',
|
|
||||||
'change-request-scheduled-application-success':
|
|
||||||
'change-request-scheduled-application-success',
|
|
||||||
'change-request-scheduled-application-failure':
|
|
||||||
'change-request-scheduled-application-failure',
|
|
||||||
'change-request-configuration-updated':
|
|
||||||
'change-request-configuration-updated',
|
|
||||||
'api-token-created': 'api-token-created',
|
|
||||||
'api-token-updated': 'api-token-updated',
|
|
||||||
'api-token-deleted': 'api-token-deleted',
|
|
||||||
'feature-favorited': 'feature-favorited',
|
|
||||||
'feature-unfavorited': 'feature-unfavorited',
|
|
||||||
'project-favorited': 'project-favorited',
|
|
||||||
'project-unfavorited': 'project-unfavorited',
|
|
||||||
'features-exported': 'features-exported',
|
|
||||||
'features-imported': 'features-imported',
|
|
||||||
'service-account-created': 'service-account-created',
|
|
||||||
'service-account-deleted': 'service-account-deleted',
|
|
||||||
'service-account-updated': 'service-account-updated',
|
|
||||||
'feature-potentially-stale-on': 'feature-potentially-stale-on',
|
|
||||||
'feature-dependency-added': 'feature-dependency-added',
|
|
||||||
'feature-dependency-removed': 'feature-dependency-removed',
|
|
||||||
'feature-dependencies-removed': 'feature-dependencies-removed',
|
|
||||||
'banner-created': 'banner-created',
|
|
||||||
'banner-updated': 'banner-updated',
|
|
||||||
'banner-deleted': 'banner-deleted',
|
|
||||||
'project-environment-added': 'project-environment-added',
|
|
||||||
'project-environment-removed': 'project-environment-removed',
|
|
||||||
'default-strategy-updated': 'default-strategy-updated',
|
|
||||||
'segment-import': 'segment-import',
|
|
||||||
'signal-endpoint-created': 'signal-endpoint-created',
|
|
||||||
'signal-endpoint-updated': 'signal-endpoint-updated',
|
|
||||||
'signal-endpoint-deleted': 'signal-endpoint-deleted',
|
|
||||||
'signal-endpoint-token-created': 'signal-endpoint-token-created',
|
|
||||||
'signal-endpoint-token-updated': 'signal-endpoint-token-updated',
|
|
||||||
'signal-endpoint-token-deleted': 'signal-endpoint-token-deleted',
|
|
||||||
'actions-created': 'actions-created',
|
|
||||||
'actions-updated': 'actions-updated',
|
|
||||||
'actions-deleted': 'actions-deleted',
|
|
||||||
'release-plan-template-created': 'release-plan-template-created',
|
|
||||||
'release-plan-template-updated': 'release-plan-template-updated',
|
|
||||||
'release-plan-template-deleted': 'release-plan-template-deleted',
|
|
||||||
'release-plan-template-archived': 'release-plan-template-archived',
|
|
||||||
'release-plan-added': 'release-plan-added',
|
|
||||||
'release-plan-removed': 'release-plan-removed',
|
|
||||||
'release-plan-milestone-started': 'release-plan-milestone-started',
|
|
||||||
'user-preference-updated': 'user-preference-updated',
|
|
||||||
'scim-users-deleted': 'scim-users-deleted',
|
|
||||||
'scim-groups-deleted': 'scim-groups-deleted',
|
|
||||||
} as const;
|
|
@ -577,8 +577,6 @@ export * from './deprecateStrategy403.js';
|
|||||||
export * from './deprecateStrategy404.js';
|
export * from './deprecateStrategy404.js';
|
||||||
export * from './deprecatedProjectOverviewSchema.js';
|
export * from './deprecatedProjectOverviewSchema.js';
|
||||||
export * from './deprecatedProjectOverviewSchemaMode.js';
|
export * from './deprecatedProjectOverviewSchemaMode.js';
|
||||||
export * from './deprecatedSearchEventsSchema.js';
|
|
||||||
export * from './deprecatedSearchEventsSchemaType.js';
|
|
||||||
export * from './disableBanner401.js';
|
export * from './disableBanner401.js';
|
||||||
export * from './disableBanner403.js';
|
export * from './disableBanner403.js';
|
||||||
export * from './disableBanner404.js';
|
export * from './disableBanner404.js';
|
||||||
|
@ -6,7 +6,6 @@ import type {
|
|||||||
IEventStore,
|
IEventStore,
|
||||||
} from '../../types/stores/event-store.js';
|
} from '../../types/stores/event-store.js';
|
||||||
import type { IApiUser, IUser } from '../../types/index.js';
|
import type { IApiUser, IUser } from '../../types/index.js';
|
||||||
import type { DeprecatedSearchEventsSchema } from '../../openapi/index.js';
|
|
||||||
import type EventEmitter from 'node:events';
|
import type EventEmitter from 'node:events';
|
||||||
import { ApiTokenType } from '../../types/model.js';
|
import { ApiTokenType } from '../../types/model.js';
|
||||||
import { EVENTS_CREATED_BY_PROCESSED } from '../../metric-events.js';
|
import { EVENTS_CREATED_BY_PROCESSED } from '../../metric-events.js';
|
||||||
@ -67,18 +66,6 @@ export default class EventService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async deprecatedSearchEvents(
|
|
||||||
search: DeprecatedSearchEventsSchema,
|
|
||||||
): Promise<IEventList> {
|
|
||||||
const totalEvents =
|
|
||||||
await this.eventStore.deprecatedFilteredCount(search);
|
|
||||||
const events = await this.eventStore.deprecatedSearchEvents(search);
|
|
||||||
return {
|
|
||||||
events,
|
|
||||||
totalEvents,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async searchEvents(
|
async searchEvents(
|
||||||
search: IEventSearchParams,
|
search: IEventSearchParams,
|
||||||
userId: number,
|
userId: number,
|
||||||
@ -96,12 +83,8 @@ export default class EventService {
|
|||||||
queryParams.push(...projectFilter);
|
queryParams.push(...projectFilter);
|
||||||
|
|
||||||
const totalEvents = await this.eventStore.searchEventsCount(
|
const totalEvents = await this.eventStore.searchEventsCount(
|
||||||
{
|
|
||||||
limit: search.limit,
|
|
||||||
offset: search.offset,
|
|
||||||
query: search.query,
|
|
||||||
},
|
|
||||||
queryParams,
|
queryParams,
|
||||||
|
search.query,
|
||||||
);
|
);
|
||||||
const events = await this.eventStore.searchEvents(
|
const events = await this.eventStore.searchEvents(
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,7 @@ import EventStore from './event-store.js';
|
|||||||
import getLogger from '../../../test/fixtures/no-logger.js';
|
import getLogger from '../../../test/fixtures/no-logger.js';
|
||||||
import { subHours, formatRFC3339 } from 'date-fns';
|
import { subHours, formatRFC3339 } from 'date-fns';
|
||||||
import dbInit from '../../../test/e2e/helpers/database-init.js';
|
import dbInit from '../../../test/e2e/helpers/database-init.js';
|
||||||
|
import { APPLICATION_CREATED } from '../../events/index.js';
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
getLogger.setMuteError(true);
|
getLogger.setMuteError(true);
|
||||||
@ -27,9 +28,19 @@ 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.deprecatedSearchEvents({
|
const events = await store.searchEvents(
|
||||||
type: 'application-created',
|
{
|
||||||
});
|
offset: 0,
|
||||||
|
limit: 10,
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
field: 'type',
|
||||||
|
operator: 'IS',
|
||||||
|
values: [APPLICATION_CREATED],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
expect(events).toBeTruthy();
|
expect(events).toBeTruthy();
|
||||||
expect(events.length).toBe(0);
|
expect(events.length).toBe(0);
|
||||||
await db.destroy();
|
await db.destroy();
|
||||||
|
@ -24,10 +24,7 @@ import {
|
|||||||
SYSTEM_USER,
|
SYSTEM_USER,
|
||||||
SYSTEM_USER_ID,
|
SYSTEM_USER_ID,
|
||||||
} from '../../types/index.js';
|
} from '../../types/index.js';
|
||||||
import type {
|
import type { ProjectActivitySchema } from '../../openapi/index.js';
|
||||||
DeprecatedSearchEventsSchema,
|
|
||||||
ProjectActivitySchema,
|
|
||||||
} from '../../openapi/index.js';
|
|
||||||
import type { IQueryParam } from '../feature-toggle/types/feature-toggle-strategies-store-type.js';
|
import type { IQueryParam } from '../feature-toggle/types/feature-toggle-strategies-store-type.js';
|
||||||
import { applyGenericQueryParams } from '../feature-search/search-utils.js';
|
import { applyGenericQueryParams } from '../feature-search/search-utils.js';
|
||||||
import type { ITag } from '../../tags/index.js';
|
import type { ITag } from '../../tags/index.js';
|
||||||
@ -142,36 +139,12 @@ class EventStore implements IEventStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deprecatedFilteredCount(
|
|
||||||
eventSearch: DeprecatedSearchEventsSchema,
|
|
||||||
): Promise<number> {
|
|
||||||
let query = this.db(TABLE);
|
|
||||||
if (eventSearch.type) {
|
|
||||||
query = query.andWhere({ type: eventSearch.type });
|
|
||||||
}
|
|
||||||
if (eventSearch.project) {
|
|
||||||
query = query.andWhere({ project: eventSearch.project });
|
|
||||||
}
|
|
||||||
if (eventSearch.feature) {
|
|
||||||
query = query.andWhere({ feature_name: eventSearch.feature });
|
|
||||||
}
|
|
||||||
const count = await query.count().first();
|
|
||||||
if (!count) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (typeof count.count === 'string') {
|
|
||||||
return Number.parseInt(count.count, 10);
|
|
||||||
} else {
|
|
||||||
return count.count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async searchEventsCount(
|
async searchEventsCount(
|
||||||
params: IEventSearchParams,
|
|
||||||
queryParams: IQueryParam[],
|
queryParams: IQueryParam[],
|
||||||
|
query?: IEventSearchParams['query'],
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const query = this.buildSearchQuery(params, queryParams);
|
const searchQuery = this.buildSearchQuery(queryParams, query);
|
||||||
const count = await query.count().first();
|
const count = await searchQuery.count().first();
|
||||||
if (!count) {
|
if (!count) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -397,7 +370,7 @@ class EventStore implements IEventStore {
|
|||||||
queryParams: IQueryParam[],
|
queryParams: IQueryParam[],
|
||||||
options?: { withIp?: boolean },
|
options?: { withIp?: boolean },
|
||||||
): Promise<IEvent[]> {
|
): Promise<IEvent[]> {
|
||||||
const query = this.buildSearchQuery(params, queryParams)
|
const query = this.buildSearchQuery(queryParams, params.query)
|
||||||
.select(options?.withIp ? [...EVENT_COLUMNS, 'ip'] : EVENT_COLUMNS)
|
.select(options?.withIp ? [...EVENT_COLUMNS, 'ip'] : EVENT_COLUMNS)
|
||||||
.orderBy('created_at', 'desc')
|
.orderBy('created_at', 'desc')
|
||||||
.limit(Number(params.limit) ?? 100)
|
.limit(Number(params.limit) ?? 100)
|
||||||
@ -415,23 +388,23 @@ class EventStore implements IEventStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private buildSearchQuery(
|
private buildSearchQuery(
|
||||||
params: IEventSearchParams,
|
|
||||||
queryParams: IQueryParam[],
|
queryParams: IQueryParam[],
|
||||||
|
query?: IEventSearchParams['query'],
|
||||||
) {
|
) {
|
||||||
let query = this.db.from<IEventTable>(TABLE);
|
let searchQuery = this.db.from<IEventTable>(TABLE);
|
||||||
|
|
||||||
applyGenericQueryParams(query, queryParams);
|
applyGenericQueryParams(searchQuery, queryParams);
|
||||||
|
|
||||||
if (params.query) {
|
if (query) {
|
||||||
query = query.where((where) =>
|
searchQuery = searchQuery.where((where) =>
|
||||||
where
|
where
|
||||||
.orWhereRaw('data::text ILIKE ?', `%${params.query}%`)
|
.orWhereRaw('data::text ILIKE ?', `%${query}%`)
|
||||||
.orWhereRaw('tags::text ILIKE ?', `%${params.query}%`)
|
.orWhereRaw('tags::text ILIKE ?', `%${query}%`)
|
||||||
.orWhereRaw('pre_data::text ILIKE ?', `%${params.query}%`),
|
.orWhereRaw('pre_data::text ILIKE ?', `%${query}%`),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return query;
|
return searchQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEventCreators(): Promise<Array<{ id: number; name: string }>> {
|
async getEventCreators(): Promise<Array<{ id: number; name: string }>> {
|
||||||
@ -483,52 +456,6 @@ class EventStore implements IEventStore {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async deprecatedSearchEvents(
|
|
||||||
search: DeprecatedSearchEventsSchema = {},
|
|
||||||
): Promise<IEvent[]> {
|
|
||||||
let query = this.db
|
|
||||||
.select(EVENT_COLUMNS)
|
|
||||||
.from<IEventTable>(TABLE)
|
|
||||||
.limit(search.limit ?? 100)
|
|
||||||
.offset(search.offset ?? 0)
|
|
||||||
.orderBy('created_at', 'desc');
|
|
||||||
|
|
||||||
if (search.type) {
|
|
||||||
query = query.andWhere({
|
|
||||||
type: search.type,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (search.project) {
|
|
||||||
query = query.andWhere({
|
|
||||||
project: search.project,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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('tags::text ILIKE ?', `%${search.query}%`)
|
|
||||||
.orWhereRaw('pre_data::text ILIKE ?', `%${search.query}%`),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return (await query).map(this.rowToEvent);
|
|
||||||
} catch (err) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rowToEvent(row: IEventTable): IEvent {
|
rowToEvent(row: IEventTable): IEvent {
|
||||||
return {
|
return {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
|
@ -492,18 +492,26 @@ export class InstanceStatsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
featuresExported(): Promise<number> {
|
featuresExported(): Promise<number> {
|
||||||
return this.memorize('deprecatedFilteredCountFeaturesExported', () =>
|
return this.memorize('searchEventsCountFeaturesExported', () =>
|
||||||
this.eventStore.deprecatedFilteredCount({
|
this.eventStore.searchEventsCount([
|
||||||
type: FEATURES_EXPORTED,
|
{
|
||||||
}),
|
field: 'type',
|
||||||
|
operator: 'IS',
|
||||||
|
values: [FEATURES_EXPORTED],
|
||||||
|
},
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
featuresImported(): Promise<number> {
|
featuresImported(): Promise<number> {
|
||||||
return this.memorize('deprecatedFilteredCountFeaturesImported', () =>
|
return this.memorize('searchEventsCountFeaturesImported', () =>
|
||||||
this.eventStore.deprecatedFilteredCount({
|
this.eventStore.searchEventsCount([
|
||||||
type: FEATURES_IMPORTED,
|
{
|
||||||
}),
|
field: 'type',
|
||||||
|
operator: 'IS',
|
||||||
|
values: [FEATURES_IMPORTED],
|
||||||
|
},
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
import type { FromSchema } from 'json-schema-to-ts';
|
|
||||||
import { IEventTypes } from '../../events/index.js';
|
|
||||||
|
|
||||||
export const deprecatedSearchEventsSchema = {
|
|
||||||
$id: '#/components/schemas/deprecatedSearchEventsSchema',
|
|
||||||
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).',
|
|
||||||
enum: IEventTypes,
|
|
||||||
example: 'feature-created',
|
|
||||||
},
|
|
||||||
project: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Find events by project ID (case-sensitive).',
|
|
||||||
example: 'default',
|
|
||||||
},
|
|
||||||
feature: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Find events by feature flag name (case-sensitive).',
|
|
||||||
example: 'my.first.flag',
|
|
||||||
},
|
|
||||||
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).`,
|
|
||||||
example: 'admin@example.com',
|
|
||||||
},
|
|
||||||
limit: {
|
|
||||||
type: 'integer',
|
|
||||||
description:
|
|
||||||
'The maximum amount of events to return in the search result',
|
|
||||||
minimum: 1,
|
|
||||||
maximum: 100,
|
|
||||||
default: 100,
|
|
||||||
example: 50,
|
|
||||||
},
|
|
||||||
offset: {
|
|
||||||
description: 'Which event id to start listing from',
|
|
||||||
type: 'integer',
|
|
||||||
minimum: 0,
|
|
||||||
default: 0,
|
|
||||||
example: 100,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: {},
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type DeprecatedSearchEventsSchema = FromSchema<
|
|
||||||
typeof deprecatedSearchEventsSchema
|
|
||||||
>;
|
|
@ -64,7 +64,6 @@ export * from './date-schema.js';
|
|||||||
export * from './dependencies-exist-schema.js';
|
export * from './dependencies-exist-schema.js';
|
||||||
export * from './dependent-feature-schema.js';
|
export * from './dependent-feature-schema.js';
|
||||||
export * from './deprecated-project-overview-schema.js';
|
export * from './deprecated-project-overview-schema.js';
|
||||||
export * from './deprecated-search-events-schema.js';
|
|
||||||
export * from './dora-features-schema.js';
|
export * from './dora-features-schema.js';
|
||||||
export * from './edge-token-schema.js';
|
export * from './edge-token-schema.js';
|
||||||
export * from './email-schema.js';
|
export * from './email-schema.js';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { Request, Response } from 'express';
|
import type { Response } from 'express';
|
||||||
import type { IUnleashConfig } from '../../types/option.js';
|
import type { IUnleashConfig } from '../../types/option.js';
|
||||||
import type { IUnleashServices } from '../../services/index.js';
|
import type { IUnleashServices } from '../../services/index.js';
|
||||||
import type EventService from '../../features/events/event-service.js';
|
import type EventService from '../../features/events/event-service.js';
|
||||||
@ -24,6 +24,7 @@ import {
|
|||||||
eventCreatorsSchema,
|
eventCreatorsSchema,
|
||||||
type ProjectFlagCreatorsSchema,
|
type ProjectFlagCreatorsSchema,
|
||||||
} from '../../openapi/index.js';
|
} from '../../openapi/index.js';
|
||||||
|
import { extractUserIdFromUser } from '../../util/index.js';
|
||||||
|
|
||||||
const ANON_KEYS = ['email', 'username', 'createdBy'];
|
const ANON_KEYS = ['email', 'username', 'createdBy'];
|
||||||
const version = 1 as const;
|
const version = 1 as const;
|
||||||
@ -53,6 +54,7 @@ export default class EventController extends Controller {
|
|||||||
permission: ADMIN,
|
permission: ADMIN,
|
||||||
middleware: [
|
middleware: [
|
||||||
openApiService.validPath({
|
openApiService.validPath({
|
||||||
|
deprecated: true,
|
||||||
operationId: 'getEvents',
|
operationId: 'getEvents',
|
||||||
tags: ['Events'],
|
tags: ['Events'],
|
||||||
responses: {
|
responses: {
|
||||||
@ -84,6 +86,7 @@ export default class EventController extends Controller {
|
|||||||
permission: NONE,
|
permission: NONE,
|
||||||
middleware: [
|
middleware: [
|
||||||
openApiService.validPath({
|
openApiService.validPath({
|
||||||
|
deprecated: true,
|
||||||
operationId: 'getEventsForToggle',
|
operationId: 'getEventsForToggle',
|
||||||
tags: ['Events'],
|
tags: ['Events'],
|
||||||
responses: {
|
responses: {
|
||||||
@ -127,15 +130,21 @@ export default class EventController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getEvents(
|
async getEvents(
|
||||||
req: Request<any, any, any, { project?: string }>,
|
req: IAuthRequest<any, any, any, { project?: string }>,
|
||||||
res: Response<EventsSchema>,
|
res: Response<EventsSchema>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { project } = req.query;
|
const { user, query } = req;
|
||||||
|
const { project } = query;
|
||||||
let eventList: IEventList;
|
let eventList: IEventList;
|
||||||
if (project) {
|
if (project) {
|
||||||
eventList = await this.eventService.deprecatedSearchEvents({
|
eventList = await this.eventService.searchEvents(
|
||||||
project,
|
{
|
||||||
});
|
project: `IS:${project}`,
|
||||||
|
offset: 0,
|
||||||
|
limit: 50,
|
||||||
|
},
|
||||||
|
extractUserIdFromUser(user),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
eventList = await this.eventService.getEvents();
|
eventList = await this.eventService.getEvents();
|
||||||
}
|
}
|
||||||
@ -155,17 +164,23 @@ export default class EventController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getEventsForToggle(
|
async getEventsForToggle(
|
||||||
req: Request<{ featureName: string }>,
|
req: IAuthRequest<{ featureName: string }>,
|
||||||
res: Response<FeatureEventsSchema>,
|
res: Response<FeatureEventsSchema>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const feature = req.params.featureName;
|
const { user, params } = req;
|
||||||
const eventList = await this.eventService.deprecatedSearchEvents({
|
const { featureName } = params;
|
||||||
feature,
|
const eventList = await this.eventService.searchEvents(
|
||||||
});
|
{
|
||||||
|
feature: `IS:${featureName}`,
|
||||||
|
offset: 0,
|
||||||
|
limit: 50,
|
||||||
|
},
|
||||||
|
extractUserIdFromUser(user),
|
||||||
|
);
|
||||||
|
|
||||||
const response = {
|
const response = {
|
||||||
version,
|
version,
|
||||||
toggleName: feature,
|
toggleName: featureName,
|
||||||
events: serializeDates(this.maybeAnonymiseEvents(eventList.events)),
|
events: serializeDates(this.maybeAnonymiseEvents(eventList.events)),
|
||||||
totalEvents: eventList.totalEvents,
|
totalEvents: eventList.totalEvents,
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
import type { IBaseEvent, IEvent } from '../../events/index.js';
|
import type { IBaseEvent, IEvent } from '../../events/index.js';
|
||||||
import type { Store } from './store.js';
|
import type { Store } from './store.js';
|
||||||
import type {
|
import type { ProjectActivitySchema } from '../../openapi/index.js';
|
||||||
DeprecatedSearchEventsSchema,
|
|
||||||
ProjectActivitySchema,
|
|
||||||
} from '../../openapi/index.js';
|
|
||||||
import type EventEmitter from 'events';
|
import type EventEmitter from 'events';
|
||||||
import type { IQueryOperations } from '../../features/events/event-store.js';
|
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';
|
||||||
@ -29,16 +26,10 @@ export interface IEventStore
|
|||||||
batchStore(events: IBaseEvent[]): Promise<void>;
|
batchStore(events: IBaseEvent[]): Promise<void>;
|
||||||
getEvents(): Promise<IEvent[]>;
|
getEvents(): Promise<IEvent[]>;
|
||||||
count(): Promise<number>;
|
count(): Promise<number>;
|
||||||
deprecatedFilteredCount(
|
|
||||||
search: DeprecatedSearchEventsSchema,
|
|
||||||
): Promise<number>;
|
|
||||||
searchEventsCount(
|
searchEventsCount(
|
||||||
params: IEventSearchParams,
|
|
||||||
queryParams: IQueryParam[],
|
queryParams: IQueryParam[],
|
||||||
|
query?: IEventSearchParams['query'],
|
||||||
): Promise<number>;
|
): Promise<number>;
|
||||||
deprecatedSearchEvents(
|
|
||||||
search: DeprecatedSearchEventsSchema,
|
|
||||||
): Promise<IEvent[]>;
|
|
||||||
searchEvents(
|
searchEvents(
|
||||||
params: IEventSearchParams,
|
params: IEventSearchParams,
|
||||||
queryParams: IQueryParam[],
|
queryParams: IQueryParam[],
|
||||||
|
@ -36,9 +36,19 @@ test('Can create new setting', async () => {
|
|||||||
|
|
||||||
expect(actual).toStrictEqual(someData);
|
expect(actual).toStrictEqual(someData);
|
||||||
const { eventStore } = stores;
|
const { eventStore } = stores;
|
||||||
const createdEvents = await eventStore.deprecatedSearchEvents({
|
const createdEvents = await eventStore.searchEvents(
|
||||||
type: SETTING_CREATED,
|
{
|
||||||
});
|
offset: 0,
|
||||||
|
limit: 10,
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
field: 'type',
|
||||||
|
operator: 'IS',
|
||||||
|
values: [SETTING_CREATED],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
expect(createdEvents).toHaveLength(1);
|
expect(createdEvents).toHaveLength(1);
|
||||||
expect(createdEvents[0].data).toEqual({ id: 'some-setting', some: 'blob' });
|
expect(createdEvents[0].data).toEqual({ id: 'some-setting', some: 'blob' });
|
||||||
});
|
});
|
||||||
@ -51,9 +61,19 @@ 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.deprecatedSearchEvents({
|
const createdEvents = await eventStore.searchEvents(
|
||||||
type: SETTING_DELETED,
|
{
|
||||||
});
|
offset: 0,
|
||||||
|
limit: 10,
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
field: 'type',
|
||||||
|
operator: 'IS',
|
||||||
|
values: [SETTING_DELETED],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
expect(createdEvents).toHaveLength(1);
|
expect(createdEvents).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -66,9 +86,19 @@ test('Sentitive SSO settings are redacted in event log', async () => {
|
|||||||
const actual = await service.get(property);
|
const actual = await service.get(property);
|
||||||
const { eventStore } = stores;
|
const { eventStore } = stores;
|
||||||
|
|
||||||
const updatedEvents = await eventStore.deprecatedSearchEvents({
|
const updatedEvents = await eventStore.searchEvents(
|
||||||
type: SETTING_UPDATED,
|
{
|
||||||
});
|
offset: 0,
|
||||||
|
limit: 10,
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
field: 'type',
|
||||||
|
operator: 'IS',
|
||||||
|
values: [SETTING_UPDATED],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
expect(updatedEvents[0].preData).toEqual({ hideEventDetails: true });
|
expect(updatedEvents[0].preData).toEqual({ hideEventDetails: true });
|
||||||
await service.delete(property, TEST_AUDIT_USER);
|
await service.delete(property, TEST_AUDIT_USER);
|
||||||
});
|
});
|
||||||
@ -83,9 +113,19 @@ test('Can update setting', async () => {
|
|||||||
TEST_AUDIT_USER,
|
TEST_AUDIT_USER,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
const updatedEvents = await eventStore.deprecatedSearchEvents({
|
const updatedEvents = await eventStore.searchEvents(
|
||||||
type: SETTING_UPDATED,
|
{
|
||||||
});
|
offset: 0,
|
||||||
|
limit: 10,
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
field: 'type',
|
||||||
|
operator: 'IS',
|
||||||
|
values: [SETTING_UPDATED],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
expect(updatedEvents).toHaveLength(1);
|
expect(updatedEvents).toHaveLength(1);
|
||||||
expect(updatedEvents[0].data).toEqual({
|
expect(updatedEvents[0].data).toEqual({
|
||||||
id: 'updated-setting',
|
id: 'updated-setting',
|
||||||
|
@ -243,13 +243,33 @@ test('Should get all events of type', async () => {
|
|||||||
return eventStore.store(event);
|
return eventStore.store(event);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const featureCreatedEvents = await eventStore.deprecatedSearchEvents({
|
const featureCreatedEvents = await eventStore.searchEvents(
|
||||||
type: FEATURE_CREATED,
|
{
|
||||||
});
|
offset: 0,
|
||||||
|
limit: 10,
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
field: 'type',
|
||||||
|
operator: 'IS',
|
||||||
|
values: [FEATURE_CREATED],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
expect(featureCreatedEvents).toHaveLength(3);
|
expect(featureCreatedEvents).toHaveLength(3);
|
||||||
const featureDeletedEvents = await eventStore.deprecatedSearchEvents({
|
const featureDeletedEvents = await eventStore.searchEvents(
|
||||||
type: FEATURE_DELETED,
|
{
|
||||||
});
|
offset: 0,
|
||||||
|
limit: 10,
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
field: 'type',
|
||||||
|
operator: 'IS',
|
||||||
|
values: [FEATURE_DELETED],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
expect(featureDeletedEvents).toHaveLength(3);
|
expect(featureDeletedEvents).toHaveLength(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
14
src/test/fixtures/fake-event-store.ts
vendored
14
src/test/fixtures/fake-event-store.ts
vendored
@ -2,10 +2,7 @@ import type { IEventStore } from '../../lib/types/stores/event-store.js';
|
|||||||
import type { IBaseEvent, IEvent } from '../../lib/events/index.js';
|
import type { IBaseEvent, IEvent } from '../../lib/events/index.js';
|
||||||
import { sharedEventEmitter } from '../../lib/util/anyEventEmitter.js';
|
import { sharedEventEmitter } from '../../lib/util/anyEventEmitter.js';
|
||||||
import type { IQueryOperations } from '../../lib/features/events/event-store.js';
|
import type { IQueryOperations } from '../../lib/features/events/event-store.js';
|
||||||
import type {
|
import type { ProjectActivitySchema } from '../../lib/openapi/index.js';
|
||||||
DeprecatedSearchEventsSchema,
|
|
||||||
ProjectActivitySchema,
|
|
||||||
} from '../../lib/openapi/index.js';
|
|
||||||
import type EventEmitter from 'events';
|
import type EventEmitter from 'events';
|
||||||
|
|
||||||
class FakeEventStore implements IEventStore {
|
class FakeEventStore implements IEventStore {
|
||||||
@ -81,12 +78,6 @@ class FakeEventStore implements IEventStore {
|
|||||||
return Promise.resolve(0);
|
return Promise.resolve(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
deprecatedFilteredCount(
|
|
||||||
search: DeprecatedSearchEventsSchema,
|
|
||||||
): Promise<number> {
|
|
||||||
return Promise.resolve(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy(): void {}
|
destroy(): void {}
|
||||||
|
|
||||||
async exists(key: number): Promise<boolean> {
|
async exists(key: number): Promise<boolean> {
|
||||||
@ -101,9 +92,6 @@ class FakeEventStore implements IEventStore {
|
|||||||
return this.events;
|
return this.events;
|
||||||
}
|
}
|
||||||
|
|
||||||
async deprecatedSearchEvents(): Promise<IEvent[]> {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
async searchEvents(): Promise<IEvent[]> {
|
async searchEvents(): Promise<IEvent[]> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user