1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-04 13:48:56 +02:00

chore!: remove deprecated POST events search endpoint (#10030)

https://linear.app/unleash/issue/2-3368/remove-post-apiadmineventssearch-deprecated-in-610

Removes POST `/api/admin/events/search` which was deprecated in v6.1.
Also cleans up related code.
This commit is contained in:
Nuno Góis 2025-05-27 09:12:36 +01:00 committed by GitHub
parent 45434109a9
commit 290ef6ca40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 129 additions and 182 deletions

View File

@ -2,17 +2,21 @@ import dbInit, {
type ITestDb, type ITestDb,
} from '../../../test/e2e/helpers/database-init.js'; } from '../../../test/e2e/helpers/database-init.js';
import { import {
createUserWithRootRole,
type IUnleashTest, type IUnleashTest,
setupAppWithCustomConfig, setupAppWithAuth,
} from '../../../test/e2e/helpers/test-helper.js'; } from '../../../test/e2e/helpers/test-helper.js';
import getLogger from '../../../test/fixtures/no-logger.js'; import getLogger from '../../../test/fixtures/no-logger.js';
import { RoleName } from '../../types/index.js';
let app: IUnleashTest; let app: IUnleashTest;
let db: ITestDb; let db: ITestDb;
const adminEmail = 'admin-user@getunleash.io';
beforeAll(async () => { beforeAll(async () => {
db = await dbInit('tag_types_api_serial', getLogger); db = await dbInit('tag_types_api_serial', getLogger);
app = await setupAppWithCustomConfig( app = await setupAppWithAuth(
db.stores, db.stores,
{ {
experimental: { experimental: {
@ -23,6 +27,17 @@ beforeAll(async () => {
}, },
db.rawDatabase, db.rawDatabase,
); );
await createUserWithRootRole({
app,
stores: db.stores,
email: adminEmail,
roleName: RoleName.ADMIN,
});
});
beforeEach(async () => {
await app.login({ email: adminEmail });
}); });
afterAll(async () => { afterAll(async () => {

View File

@ -18,8 +18,6 @@ import {
type FeatureEventsSchema, type FeatureEventsSchema,
} from '../../../lib/openapi/spec/feature-events-schema.js'; } from '../../../lib/openapi/spec/feature-events-schema.js';
import { getStandardResponses } from '../../../lib/openapi/util/standard-responses.js'; import { getStandardResponses } from '../../../lib/openapi/util/standard-responses.js';
import { createRequestSchema } from '../../openapi/util/create-request-schema.js';
import type { DeprecatedSearchEventsSchema } from '../../openapi/spec/deprecated-search-events-schema.js';
import type { IFlagResolver } from '../../types/experimental.js'; import type { IFlagResolver } from '../../types/experimental.js';
import type { IAuthRequest } from '../unleash-types.js'; import type { IAuthRequest } from '../unleash-types.js';
import { import {
@ -100,27 +98,6 @@ export default class EventController extends Controller {
], ],
}); });
this.route({
method: 'post',
path: '/events/search',
handler: this.deprecatedSearchEvents,
permission: NONE,
middleware: [
openApiService.validPath({
operationId: 'deprecatedSearchEvents',
tags: ['Events'],
deprecated: true,
summary: 'Search for events (deprecated)',
description:
'Allows searching for events matching the search criteria in the request body',
requestBody: createRequestSchema(
'deprecatedSearchEventsSchema',
),
responses: { 200: createResponseSchema('eventsSchema') },
}),
],
});
this.route({ this.route({
method: 'get', method: 'get',
path: '/event-creators', path: '/event-creators',
@ -201,28 +178,6 @@ export default class EventController extends Controller {
); );
} }
async deprecatedSearchEvents(
req: Request<unknown, unknown, DeprecatedSearchEventsSchema>,
res: Response<EventsSchema>,
): Promise<void> {
const eventList = await this.eventService.deprecatedSearchEvents(
req.body,
);
const response = {
version,
events: serializeDates(this.maybeAnonymiseEvents(eventList.events)),
totalEvents: eventList.totalEvents,
};
this.openApiService.respondWithValidation(
200,
res,
featureEventsSchema.$id,
response,
);
}
async getEventCreators( async getEventCreators(
req: IAuthRequest, req: IAuthRequest,
res: Response<ProjectFlagCreatorsSchema>, res: Response<ProjectFlagCreatorsSchema>,

View File

@ -6,66 +6,26 @@ import {
type IUnleashStores, type IUnleashStores,
RoleName, RoleName,
} from '../../../../lib/types/index.js'; } from '../../../../lib/types/index.js';
import type { import type { EventService } from '../../../../lib/services/index.js';
AccessService,
EventService,
} from '../../../../lib/services/index.js';
import getLogger from '../../../fixtures/no-logger.js'; import getLogger from '../../../fixtures/no-logger.js';
import { import {
createUserWithRootRole,
type IUnleashTest, type IUnleashTest,
setupAppWithAuth, setupAppWithAuth,
} from '../../helpers/test-helper.js'; } from '../../helpers/test-helper.js';
import { createEventsService } from '../../../../lib/features/index.js'; import { createEventsService } from '../../../../lib/features/index.js';
import { createTestConfig } from '../../../config/test-config.js'; import { createTestConfig } from '../../../config/test-config.js';
import type { IRole } from '../../../../lib/types/stores/access-store.js';
import { FEATURE_CREATED, USER_CREATED } from '../../../../lib/events/index.js'; import { FEATURE_CREATED, USER_CREATED } from '../../../../lib/events/index.js';
let app: IUnleashTest; let app: IUnleashTest;
let db: ITestDb; let db: ITestDb;
let eventService: EventService; let eventService: EventService;
const TEST_USER_ID = -9999; const TEST_USER_ID = -9999;
const regularUserName = 'import-user'; const regularEmail = 'import-user@getunleash.io';
const adminUserName = 'admin-user'; const adminEmail = 'admin-user@getunleash.io';
const config: IUnleashConfig = createTestConfig(); const config: IUnleashConfig = createTestConfig();
let adminRole: IRole;
let stores: IUnleashStores; let stores: IUnleashStores;
let accessService: AccessService;
const loginRegularUser = () =>
app.request
.post(`/auth/demo/login`)
.send({
email: `${regularUserName}@getunleash.io`,
})
.expect(200);
const loginAdminUser = () =>
app.request
.post(`/auth/demo/login`)
.send({
email: `${adminUserName}@getunleash.io`,
})
.expect(200);
const createUserEditorAccess = async (name, email) => {
const { userStore } = stores;
const user = await userStore.insert({
name,
email,
});
return user;
};
const createUserAdminAccess = async (name, email) => {
const { userStore } = stores;
const user = await userStore.insert({
name,
email,
});
await accessService.addUserToRole(user.id, adminRole.id, 'default');
return user;
};
beforeAll(async () => { beforeAll(async () => {
db = await dbInit('event_search', getLogger); db = await dbInit('event_search', getLogger);
@ -84,19 +44,18 @@ beforeAll(async () => {
eventService = createEventsService(db.rawDatabase, config); eventService = createEventsService(db.rawDatabase, config);
accessService = app.services.accessService; await createUserWithRootRole({
app,
stores,
email: regularEmail,
});
const roles = await accessService.getRootRoles(); await createUserWithRootRole({
adminRole = roles.find((role) => role.name === RoleName.ADMIN)!; app,
stores,
await createUserEditorAccess( email: adminEmail,
regularUserName, roleName: RoleName.ADMIN,
`${regularUserName}@getunleash.io`, });
);
await createUserAdminAccess(
adminUserName,
`${adminUserName}@getunleash.io`,
);
}); });
afterAll(async () => { afterAll(async () => {
@ -105,7 +64,7 @@ afterAll(async () => {
}); });
beforeEach(async () => { beforeEach(async () => {
await loginAdminUser(); await app.login({ email: adminEmail });
await db.stores.featureToggleStore.deleteAll(); await db.stores.featureToggleStore.deleteAll();
await db.stores.segmentStore.deleteAll(); await db.stores.segmentStore.deleteAll();
await db.stores.eventStore.deleteAll(); await db.stores.eventStore.deleteAll();
@ -522,7 +481,7 @@ test('should filter events by project using IS_ANY_OF', async () => {
}); });
test('should not show user creation events for non-admins', async () => { test('should not show user creation events for non-admins', async () => {
await loginRegularUser(); await app.login({ email: regularEmail });
await eventService.storeEvent({ await eventService.storeEvent({
type: USER_CREATED, type: USER_CREATED,
createdBy: 'test-user', createdBy: 'test-user',

View File

@ -90,75 +90,6 @@ test('Can filter by project', async () => {
}); });
}); });
test('can search for events', async () => {
const events: IBaseEvent[] = [
{
type: FEATURE_CREATED,
project: randomId(),
data: { id: randomId() },
tags: [],
createdBy: randomId(),
createdByUserId: TEST_USER_ID,
ip: '127.0.0.1',
},
{
type: FEATURE_CREATED,
project: randomId(),
data: { id: randomId() },
preData: { id: randomId() },
tags: [{ type: 'simple', value: randomId() }],
createdBy: randomId(),
createdByUserId: TEST_USER_ID,
ip: '127.0.0.1',
},
];
await Promise.all(
events.map((event) => {
return eventService.storeEvent(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);
});
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);
});
await app.request
.post('/api/admin/events/search')
.send({ query: events[1].tags![0].value })
.expect(200)
.expect((res) => {
expect(res.body.events).toHaveLength(1);
expect(res.body.events[0].data.id).toEqual(events[1].data.id);
});
});
test('event creators - if system user, return system name, else should return name from database if user exists, else from events table', async () => { test('event creators - if system user, return system name, else should return name from database if user exists, else from events table', async () => {
const user = await db.stores.userStore.insert({ name: 'database-user' }); const user = await db.stores.userStore.insert({ name: 'database-user' });
const events: IBaseEvent[] = [ const events: IBaseEvent[] = [

View File

@ -1,18 +1,22 @@
import dbInit, { type ITestDb } from '../../helpers/database-init.js'; import dbInit, { type ITestDb } from '../../helpers/database-init.js';
import getLogger from '../../../fixtures/no-logger.js'; import getLogger from '../../../fixtures/no-logger.js';
import { import {
createUserWithRootRole,
type IUnleashTest, type IUnleashTest,
setupAppWithCustomConfig, setupAppWithAuth,
} from '../../helpers/test-helper.js'; } from '../../helpers/test-helper.js';
import { validateSchema } from '../../../../lib/openapi/validate.js'; import { validateSchema } from '../../../../lib/openapi/validate.js';
import { featureTypesSchema } from '../../../../lib/openapi/spec/feature-types-schema.js'; import { featureTypesSchema } from '../../../../lib/openapi/spec/feature-types-schema.js';
import { RoleName } from '../../../../lib/types/index.js';
let app: IUnleashTest; let app: IUnleashTest;
let db: ITestDb; let db: ITestDb;
const adminEmail = 'admin-user@getunleash.io';
beforeAll(async () => { beforeAll(async () => {
db = await dbInit('feature_type_api_serial', getLogger); db = await dbInit('feature_type_api_serial', getLogger);
app = await setupAppWithCustomConfig( app = await setupAppWithAuth(
db.stores, db.stores,
{ {
experimental: { experimental: {
@ -23,6 +27,17 @@ beforeAll(async () => {
}, },
db.rawDatabase, db.rawDatabase,
); );
await createUserWithRootRole({
app,
stores: db.stores,
email: adminEmail,
roleName: RoleName.ADMIN,
});
});
beforeEach(async () => {
await app.login({ email: adminEmail });
}); });
afterAll(async () => { afterAll(async () => {

View File

@ -24,9 +24,23 @@ import type { Knex } from 'knex';
import type TestAgent from 'supertest/lib/agent.d.ts'; import type TestAgent from 'supertest/lib/agent.d.ts';
import type Test from 'supertest/lib/test.d.ts'; import type Test from 'supertest/lib/test.d.ts';
import type { Server } from 'node:http'; import type { Server } from 'node:http';
import { initialServiceSetup } from '../../../lib/server-impl.js'; import {
initialServiceSetup,
type IUser,
type RoleName,
} from '../../../lib/server-impl.js';
import type { EventSearchQueryParameters } from '../../../lib/openapi/spec/event-search-query-parameters.js';
process.env.NODE_ENV = 'test'; process.env.NODE_ENV = 'test';
type DemoLoginArgs = {
email: string;
};
type SimpleLoginArgs = {
username: string;
password: string;
};
export interface IUnleashTest extends IUnleashHttpAPI { export interface IUnleashTest extends IUnleashHttpAPI {
request: TestAgent<Test>; request: TestAgent<Test>;
destroy: () => Promise<void>; destroy: () => Promise<void>;
@ -115,7 +129,10 @@ export interface IUnleashHttpAPI {
expectedResponseCode?: number, expectedResponseCode?: number,
): supertest.Test; ): supertest.Test;
getRecordedEvents(): supertest.Test; getRecordedEvents(
queryParams?: EventSearchQueryParameters,
expectedResponseCode?: number,
): supertest.Test;
createSegment(postData: object, expectStatusCode?: number): supertest.Test; createSegment(postData: object, expectStatusCode?: number): supertest.Test;
deleteSegment( deleteSegment(
@ -127,6 +144,8 @@ export interface IUnleashHttpAPI {
postData: object, postData: object,
expectStatusCode?: number, expectStatusCode?: number,
): supertest.Test; ): supertest.Test;
login(args: DemoLoginArgs | SimpleLoginArgs): supertest.Test;
} }
function httpApis( function httpApis(
@ -318,15 +337,32 @@ function httpApis(
.expect(expectStatusCode); .expect(expectStatusCode);
}, },
getRecordedEvents( getRecordedEvents(
project: string | null = null, queryParams: EventSearchQueryParameters = {},
expectedResponseCode: number = 200, expectedResponseCode: number = 200,
): supertest.Test { ): supertest.Test {
const query = new URLSearchParams(queryParams as any).toString();
return request return request
.post('/api/admin/events/search') .get(`/api/admin/search/events${query ? `?${query}` : ''}`)
.send({ project, query: '', limit: 50, offset: 0 })
.set('Content-Type', 'application/json')
.expect(expectedResponseCode); .expect(expectedResponseCode);
}, },
login(args: DemoLoginArgs | SimpleLoginArgs): supertest.Test {
if ('email' in args) {
const { email } = args;
return request
.post(`${base}/auth/demo/login`)
.send({ email })
.expect(200);
}
const { username, password } = args;
return request
.post(`${base}/auth/simple/login`)
.send({
username,
password,
} as SimpleLoginArgs)
.expect(200);
},
}; };
} }
@ -509,3 +545,39 @@ export const insertFeatureEnvironmentsLastSeen = async (
return date; return date;
}; };
export const createUserWithRootRole = async ({
app,
stores,
email,
name = email,
roleName,
}: {
app: IUnleashTest;
stores: IUnleashStores;
name?: string;
email: string;
roleName?: RoleName;
}): Promise<IUser> => {
const createdUser = await stores.userStore.insert({
name,
email,
});
if (roleName) {
const roles = await app.services.accessService.getRootRoles();
const role = roles.find((role) => role.name === roleName);
if (!role) {
throw new Error(`Role ${roleName} not found`);
}
await app.services.accessService.addUserToRole(
createdUser.id,
role.id,
'default',
);
}
return createdUser;
};