mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
chore: integration events API (#7639)
https://linear.app/unleash/issue/2-2439/create-new-integration-events-endpoint https://linear.app/unleash/issue/2-2436/create-new-integration-event-openapi-schemas This adds a new `/events` endpoint to the Addons API, allowing us to fetch integration events for a specific integration configuration id. ![image](https://github.com/user-attachments/assets/e95b669e-e498-40c0-9d66-55be30a24c13) Also includes: - `IntegrationEventsSchema`: New schema to represent the response object of the list of integration events; - `yarn schema:update`: New `package.json` script to update the OpenAPI spec file; - `BasePaginationParameters`: This is copied from Enterprise. After merging this we should be able to refactor Enterprise to use this one instead of the one it has, so we don't repeat ourselves; We're also now correctly representing the BIGSERIAL as BigInt (string + pattern) in our OpenAPI schema. Otherwise our validation would complain, since we're saying it's a number in the schema but in fact returning a string.
This commit is contained in:
parent
9ff393b3d7
commit
1d6dc9b195
@ -20,9 +20,7 @@ fs.readdir(directoryPath, (err, files) => {
|
||||
const script = path.basename(__filename);
|
||||
const message = `/**
|
||||
* Auto-generated file by ${script}. Do not edit.
|
||||
* To run it manually execute \`node ${script}\` from ${path.basename(
|
||||
__dirname,
|
||||
)}
|
||||
* To run it manually execute \`yarn schema:update\` or \`node ${path.basename(__dirname)}/${script}\`
|
||||
*/\n`;
|
||||
fs.writeFileSync(indexPath, `${message}${exports}\n${message}`, (err) => {
|
||||
if (err) {
|
||||
|
@ -63,7 +63,8 @@
|
||||
"clean": "del-cli --force dist",
|
||||
"preversion": "./scripts/check-release.sh",
|
||||
"heroku-postbuild": "cd frontend && yarn && yarn build",
|
||||
"prepack": "./scripts/prepack.sh"
|
||||
"prepack": "./scripts/prepack.sh",
|
||||
"schema:update": "node ./.husky/update-openapi-spec-list.js"
|
||||
},
|
||||
"jest-junit": {
|
||||
"suiteName": "Unleash Unit Tests",
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { CRUDStore, type CrudStoreConfig } from '../../db/crud/crud-store';
|
||||
import type { Row } from '../../db/crud/row-type';
|
||||
import type { Db } from '../../db/db';
|
||||
import type { IntegrationEventSchema } from '../../openapi/spec/integration-event-schema';
|
||||
|
||||
@ -11,7 +12,10 @@ export type IntegrationEventState = IntegrationEventWriteModel['state'];
|
||||
|
||||
export class IntegrationEventsStore extends CRUDStore<
|
||||
IntegrationEventSchema,
|
||||
IntegrationEventWriteModel
|
||||
IntegrationEventWriteModel,
|
||||
Row<IntegrationEventSchema>,
|
||||
Row<IntegrationEventWriteModel>,
|
||||
string
|
||||
> {
|
||||
constructor(db: Db, config: CrudStoreConfig) {
|
||||
super('integration_events', db, config);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import dbInit, { type ITestDb } from '../../../test/e2e/helpers/database-init';
|
||||
import {
|
||||
type IUnleashTest,
|
||||
setupAppWithAuth,
|
||||
setupAppWithCustomConfig,
|
||||
} from '../../../test/e2e/helpers/test-helper';
|
||||
import getLogger from '../../../test/fixtures/no-logger';
|
||||
import { TEST_AUDIT_USER } from '../../types';
|
||||
@ -37,7 +37,7 @@ const EVENT_FAILED: IntegrationEventWriteModel = {
|
||||
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('integration_events', getLogger);
|
||||
app = await setupAppWithAuth(
|
||||
app = await setupAppWithCustomConfig(
|
||||
db.stores,
|
||||
{
|
||||
experimental: {
|
||||
@ -192,3 +192,16 @@ test('clean up events, keeping the last 100 events', async () => {
|
||||
|
||||
expect(events).toHaveLength(100);
|
||||
});
|
||||
|
||||
test('return events from the API', async () => {
|
||||
await integrationEventsService.registerEvent(getTestEventSuccess());
|
||||
await integrationEventsService.registerEvent(getTestEventFailed());
|
||||
|
||||
const { body } = await app.request.get(
|
||||
`/api/admin/addons/${integrationId}/events`,
|
||||
);
|
||||
|
||||
expect(body.integrationEvents).toHaveLength(2);
|
||||
expect(body.integrationEvents[0].state).toBe('failed');
|
||||
expect(body.integrationEvents[1].state).toBe('success');
|
||||
});
|
||||
|
28
src/lib/openapi/spec/base-pagination-parameters.ts
Normal file
28
src/lib/openapi/spec/base-pagination-parameters.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import type { FromQueryParams } from '../util/from-query-params';
|
||||
|
||||
export const basePaginationParameters = [
|
||||
{
|
||||
name: 'limit',
|
||||
schema: {
|
||||
type: 'string',
|
||||
example: '50',
|
||||
},
|
||||
description:
|
||||
'The number of results to return in a page. By default it is set to 50.',
|
||||
in: 'query',
|
||||
},
|
||||
{
|
||||
name: 'offset',
|
||||
schema: {
|
||||
type: 'string',
|
||||
example: '50',
|
||||
},
|
||||
description:
|
||||
'The number of results to skip when returning a page. By default it is set to 0.',
|
||||
in: 'query',
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type BasePaginationParameters = Partial<
|
||||
FromQueryParams<typeof basePaginationParameters>
|
||||
>;
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated file by update-openapi-spec-list.js. Do not edit.
|
||||
* To run it manually execute `node update-openapi-spec-list.js` from .husky
|
||||
* To run it manually execute `yarn schema:update` or `node .husky/update-openapi-spec-list.js`
|
||||
*/
|
||||
export * from './addon-create-update-schema';
|
||||
export * from './addon-parameter-schema';
|
||||
@ -112,6 +112,7 @@ export * from './inactive-user-schema';
|
||||
export * from './inactive-users-schema';
|
||||
export * from './instance-admin-stats-schema';
|
||||
export * from './integration-event-schema';
|
||||
export * from './integration-events-schema';
|
||||
export * from './legal-value-schema';
|
||||
export * from './login-schema';
|
||||
export * from './maintenance-schema';
|
||||
@ -210,5 +211,5 @@ export * from './variants-schema';
|
||||
export * from './version-schema';
|
||||
/**
|
||||
* Auto-generated file by update-openapi-spec-list.js. Do not edit.
|
||||
* To run it manually execute `node update-openapi-spec-list.js` from .husky
|
||||
* To run it manually execute `yarn schema:update` or `node .husky/update-openapi-spec-list.js`
|
||||
*/
|
||||
|
@ -19,11 +19,11 @@ export const integrationEventSchema = {
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
type: 'string',
|
||||
pattern: '^[0-9]+$', // BigInt
|
||||
description:
|
||||
"The integration event's ID. Integration event IDs are incrementing integers. In other words, a more recently created integration event will always have a higher ID than an older one.",
|
||||
minimum: 1,
|
||||
example: 7,
|
||||
"The integration event's ID. Integration event IDs are incrementing integers. In other words, a more recently created integration event will always have a higher ID than an older one. This ID is represented as a string since it is a BigInt.",
|
||||
example: '7',
|
||||
},
|
||||
integrationId: {
|
||||
type: 'integer',
|
||||
|
34
src/lib/openapi/spec/integration-events-schema.ts
Normal file
34
src/lib/openapi/spec/integration-events-schema.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import type { FromSchema } from 'json-schema-to-ts';
|
||||
import { eventSchema } from './event-schema';
|
||||
import { tagSchema } from './tag-schema';
|
||||
import { variantSchema } from './variant-schema';
|
||||
import { integrationEventSchema } from './integration-event-schema';
|
||||
|
||||
export const integrationEventsSchema = {
|
||||
$id: '#/components/schemas/integrationEventsSchema',
|
||||
description: 'A response model with a list of integration events.',
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['integrationEvents'],
|
||||
properties: {
|
||||
integrationEvents: {
|
||||
type: 'array',
|
||||
description: 'A list of integration events.',
|
||||
items: {
|
||||
$ref: integrationEventSchema.$id,
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
schemas: {
|
||||
integrationEventSchema,
|
||||
eventSchema,
|
||||
tagSchema,
|
||||
variantSchema,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type IntegrationEventsSchema = FromSchema<
|
||||
typeof integrationEventsSchema
|
||||
>;
|
@ -1,10 +1,15 @@
|
||||
import type { Request, Response } from 'express';
|
||||
import Controller from '../controller';
|
||||
import type { IUnleashConfig, IUnleashServices } from '../../types';
|
||||
import type {
|
||||
IFlagResolver,
|
||||
IUnleashConfig,
|
||||
IUnleashServices,
|
||||
} from '../../types';
|
||||
import type { Logger } from '../../logger';
|
||||
import type AddonService from '../../services/addon-service';
|
||||
|
||||
import {
|
||||
ADMIN,
|
||||
CREATE_ADDON,
|
||||
DELETE_ADDON,
|
||||
NONE,
|
||||
@ -25,8 +30,21 @@ import {
|
||||
getStandardResponses,
|
||||
} from '../../openapi/util/standard-responses';
|
||||
import type { AddonCreateUpdateSchema } from '../../openapi/spec/addon-create-update-schema';
|
||||
import {
|
||||
type BasePaginationParameters,
|
||||
basePaginationParameters,
|
||||
} from '../../openapi/spec/base-pagination-parameters';
|
||||
import {
|
||||
type IntegrationEventsSchema,
|
||||
integrationEventsSchema,
|
||||
} from '../../openapi/spec/integration-events-schema';
|
||||
import { BadDataError, NotFoundError } from '../../error';
|
||||
import type { IntegrationEventsService } from '../../services';
|
||||
|
||||
type AddonServices = Pick<IUnleashServices, 'addonService' | 'openApiService'>;
|
||||
type AddonServices = Pick<
|
||||
IUnleashServices,
|
||||
'addonService' | 'openApiService' | 'integrationEventsService'
|
||||
>;
|
||||
|
||||
const PATH = '/';
|
||||
|
||||
@ -37,14 +55,24 @@ class AddonController extends Controller {
|
||||
|
||||
private openApiService: OpenApiService;
|
||||
|
||||
private integrationEventsService: IntegrationEventsService;
|
||||
|
||||
private flagResolver: IFlagResolver;
|
||||
|
||||
constructor(
|
||||
config: IUnleashConfig,
|
||||
{ addonService, openApiService }: AddonServices,
|
||||
{
|
||||
addonService,
|
||||
openApiService,
|
||||
integrationEventsService,
|
||||
}: AddonServices,
|
||||
) {
|
||||
super(config);
|
||||
this.logger = config.getLogger('/admin-api/addon.ts');
|
||||
this.addonService = addonService;
|
||||
this.openApiService = openApiService;
|
||||
this.integrationEventsService = integrationEventsService;
|
||||
this.flagResolver = config.flagResolver;
|
||||
|
||||
this.route({
|
||||
method: 'get',
|
||||
@ -149,6 +177,28 @@ Note: passing \`null\` as a value for the description property will set it to an
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
this.route({
|
||||
method: 'get',
|
||||
path: `${PATH}:id/events`,
|
||||
handler: this.getIntegrationEvents,
|
||||
permission: ADMIN,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['Unstable'],
|
||||
operationId: 'getIntegrationEvents',
|
||||
summary:
|
||||
'Get integration events for a specific integration configuration.',
|
||||
description:
|
||||
'Returns a list of integration events belonging to a specific integration configuration, identified by its id.',
|
||||
parameters: [...basePaginationParameters],
|
||||
responses: {
|
||||
...getStandardResponses(401, 403, 404),
|
||||
200: createResponseSchema(integrationEventsSchema.$id),
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
async getAddons(req: Request, res: Response<AddonsSchema>): Promise<void> {
|
||||
@ -216,6 +266,48 @@ Note: passing \`null\` as a value for the description property will set it to an
|
||||
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
async getIntegrationEvents(
|
||||
req: IAuthRequest<
|
||||
{ id: number },
|
||||
unknown,
|
||||
unknown,
|
||||
BasePaginationParameters
|
||||
>,
|
||||
res: Response<IntegrationEventsSchema>,
|
||||
): Promise<void> {
|
||||
if (!this.flagResolver.isEnabled('integrationEvents')) {
|
||||
throw new NotFoundError('This feature is not enabled');
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
|
||||
if (Number.isNaN(Number(id))) {
|
||||
throw new BadDataError('Invalid integration configuration id');
|
||||
}
|
||||
|
||||
const { limit = '50', offset = '0' } = req.query;
|
||||
|
||||
const normalizedLimit =
|
||||
Number(limit) > 0 && Number(limit) <= 100 ? Number(limit) : 50;
|
||||
const normalizedOffset = Number(offset) > 0 ? Number(offset) : 0;
|
||||
|
||||
const integrationEvents =
|
||||
await this.integrationEventsService.getPaginatedEvents(
|
||||
id,
|
||||
normalizedLimit,
|
||||
normalizedOffset,
|
||||
);
|
||||
|
||||
this.openApiService.respondWithValidation(
|
||||
200,
|
||||
res,
|
||||
integrationEventsSchema.$id,
|
||||
{
|
||||
integrationEvents: serializeDates(integrationEvents),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
export default AddonController;
|
||||
module.exports = AddonController;
|
||||
|
Loading…
Reference in New Issue
Block a user