From 6645e747070e1b7bf687227776acda2ae136fc75 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 30 Oct 2024 13:54:58 +0100 Subject: [PATCH] [ResponseOps][MaintenanceWindow] Introduce pagination for MW find API (#197172) Fixes: https://github.com/elastic/kibana/issues/193076 This PR introduce pagination for our MW find API. How to test: Use postman/insomnia/curl. Do not forget to add this header: `x-elastic-internal-origin: Kibana`, because this endpoint in internal. Basically you need to do something like this: ``` GET http://localhost:5601/top/internal/alerting/rules/maintenance_window/_find?page=3&per_page=3 ``` Try different page and per_page combination. Try without them. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../maintenance_window/apis/find/index.ts | 18 ++- .../apis/find/schemas/latest.ts | 8 ++ .../apis/find/schemas/v1.ts | 53 +++++++++ .../apis/find/types/latest.ts | 2 +- .../maintenance_window/apis/find/types/v1.ts | 18 +-- .../maintenance_window/response/schemas/v1.ts | 2 +- .../services/maintenance_windows_api/find.ts | 2 +- .../find/find_maintenance_windows.test.ts | 47 +++++++- .../methods/find/find_maintenance_windows.ts | 24 +++- .../find_maintenance_window_params_schema.ts | 13 ++ .../find_maintenance_windows_result_schema.ts | 3 + .../methods/find/schemas/index.ts | 1 + .../types/find_maintenance_window_params.ts | 11 ++ .../methods/find/types/index.ts | 1 + .../methods/find_maintenance_window_so.ts | 2 +- .../maintenance_window_client.ts | 8 +- .../find_maintenance_windows_route.test.ts | 48 +++++++- .../find/find_maintenance_windows_route.ts | 47 ++++++-- .../apis/find/transforms/index.ts | 12 ++ .../latest.ts | 8 ++ .../v1.ts | 16 +++ .../latest.ts | 8 ++ .../v1.ts | 24 ++++ .../find_maintenance_windows.ts | 112 ++++++++++++++++++ 24 files changed, 455 insertions(+), 33 deletions(-) create mode 100644 x-pack/plugins/alerting/common/routes/maintenance_window/apis/find/schemas/latest.ts create mode 100644 x-pack/plugins/alerting/common/routes/maintenance_window/apis/find/schemas/v1.ts create mode 100644 x-pack/plugins/alerting/server/application/maintenance_window/methods/find/schemas/find_maintenance_window_params_schema.ts create mode 100644 x-pack/plugins/alerting/server/application/maintenance_window/methods/find/types/find_maintenance_window_params.ts create mode 100644 x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/transforms/index.ts create mode 100644 x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/transforms/transform_find_maintenance_window_params/latest.ts create mode 100644 x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/transforms/transform_find_maintenance_window_params/v1.ts create mode 100644 x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/transforms/transform_find_maintenance_window_to_response/latest.ts create mode 100644 x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/transforms/transform_find_maintenance_window_to_response/v1.ts diff --git a/x-pack/plugins/alerting/common/routes/maintenance_window/apis/find/index.ts b/x-pack/plugins/alerting/common/routes/maintenance_window/apis/find/index.ts index c3e30072e7348..8715e176f9935 100644 --- a/x-pack/plugins/alerting/common/routes/maintenance_window/apis/find/index.ts +++ b/x-pack/plugins/alerting/common/routes/maintenance_window/apis/find/index.ts @@ -5,6 +5,20 @@ * 2.0. */ -export type { FindMaintenanceWindowsResponse } from './types/latest'; +export { + findMaintenanceWindowsRequestQuerySchema, + findMaintenanceWindowsResponseBodySchema, +} from './schemas/latest'; +export type { + FindMaintenanceWindowsRequestQuery, + FindMaintenanceWindowsResponse, +} from './types/latest'; -export type { FindMaintenanceWindowsResponse as FindMaintenanceWindowsResponseV1 } from './types/v1'; +export { + findMaintenanceWindowsRequestQuerySchema as findMaintenanceWindowsRequestQuerySchemaV1, + findMaintenanceWindowsResponseBodySchema as findMaintenanceWindowsResponseBodySchemaV1, +} from './schemas/v1'; +export type { + FindMaintenanceWindowsRequestQuery as FindMaintenanceWindowsRequestQueryV1, + FindMaintenanceWindowsResponse as FindMaintenanceWindowsResponseV1, +} from './types/v1'; diff --git a/x-pack/plugins/alerting/common/routes/maintenance_window/apis/find/schemas/latest.ts b/x-pack/plugins/alerting/common/routes/maintenance_window/apis/find/schemas/latest.ts new file mode 100644 index 0000000000000..25300c97a6d2e --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/maintenance_window/apis/find/schemas/latest.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './v1'; diff --git a/x-pack/plugins/alerting/common/routes/maintenance_window/apis/find/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/maintenance_window/apis/find/schemas/v1.ts new file mode 100644 index 0000000000000..0c79ae2480215 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/maintenance_window/apis/find/schemas/v1.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { maintenanceWindowResponseSchemaV1 } from '../../../response'; + +const MAX_DOCS = 10000; + +export const findMaintenanceWindowsRequestQuerySchema = schema.object( + { + page: schema.maybe( + schema.number({ + defaultValue: 1, + min: 1, + max: MAX_DOCS, + meta: { + description: 'The page number to return.', + }, + }) + ), + per_page: schema.maybe( + schema.number({ + defaultValue: 20, + min: 0, + max: 100, + meta: { + description: 'The number of maintenance windows to return per page.', + }, + }) + ), + }, + { + validate: (params) => { + const pageAsNumber = params.page ?? 0; + const perPageAsNumber = params.per_page ?? 0; + + if (Math.max(pageAsNumber, pageAsNumber * perPageAsNumber) > MAX_DOCS) { + return `The number of documents is too high. Paginating through more than ${MAX_DOCS} documents is not possible.`; + } + }, + } +); + +export const findMaintenanceWindowsResponseBodySchema = schema.object({ + page: schema.number(), + per_page: schema.number(), + total: schema.number(), + data: schema.arrayOf(maintenanceWindowResponseSchemaV1), +}); diff --git a/x-pack/plugins/alerting/common/routes/maintenance_window/apis/find/types/latest.ts b/x-pack/plugins/alerting/common/routes/maintenance_window/apis/find/types/latest.ts index 4741df5c6c6c1..25300c97a6d2e 100644 --- a/x-pack/plugins/alerting/common/routes/maintenance_window/apis/find/types/latest.ts +++ b/x-pack/plugins/alerting/common/routes/maintenance_window/apis/find/types/latest.ts @@ -5,4 +5,4 @@ * 2.0. */ -export type { FindMaintenanceWindowsResponse } from './v1'; +export * from './v1'; diff --git a/x-pack/plugins/alerting/common/routes/maintenance_window/apis/find/types/v1.ts b/x-pack/plugins/alerting/common/routes/maintenance_window/apis/find/types/v1.ts index 0bdff90d3419f..0176d2e6689f8 100644 --- a/x-pack/plugins/alerting/common/routes/maintenance_window/apis/find/types/v1.ts +++ b/x-pack/plugins/alerting/common/routes/maintenance_window/apis/find/types/v1.ts @@ -5,11 +5,15 @@ * 2.0. */ -import { MaintenanceWindowResponseV1 } from '../../../response'; +import { TypeOf } from '@kbn/config-schema'; +import { + findMaintenanceWindowsResponseBodySchema, + findMaintenanceWindowsRequestQuerySchema, +} from '..'; -export interface FindMaintenanceWindowsResponse { - body: { - data: MaintenanceWindowResponseV1[]; - total: number; - }; -} +export type FindMaintenanceWindowsResponse = TypeOf< + typeof findMaintenanceWindowsResponseBodySchema +>; +export type FindMaintenanceWindowsRequestQuery = TypeOf< + typeof findMaintenanceWindowsRequestQuerySchema +>; diff --git a/x-pack/plugins/alerting/common/routes/maintenance_window/response/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/maintenance_window/response/schemas/v1.ts index 648b2b806978f..a9237e6be4ecc 100644 --- a/x-pack/plugins/alerting/common/routes/maintenance_window/response/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/maintenance_window/response/schemas/v1.ts @@ -6,7 +6,7 @@ */ import { schema } from '@kbn/config-schema'; -import { maintenanceWindowStatusV1 } from '..'; +import { maintenanceWindowStatus as maintenanceWindowStatusV1 } from '../constants/v1'; import { maintenanceWindowCategoryIdsSchemaV1 } from '../../shared'; import { rRuleResponseSchemaV1 } from '../../../r_rule'; import { alertsFilterQuerySchemaV1 } from '../../../alerts_filter_query'; diff --git a/x-pack/plugins/alerting/public/services/maintenance_windows_api/find.ts b/x-pack/plugins/alerting/public/services/maintenance_windows_api/find.ts index c63e491198ce9..822fb6e2bae1f 100644 --- a/x-pack/plugins/alerting/public/services/maintenance_windows_api/find.ts +++ b/x-pack/plugins/alerting/public/services/maintenance_windows_api/find.ts @@ -16,7 +16,7 @@ export async function findMaintenanceWindows({ }: { http: HttpSetup; }): Promise { - const res = await http.get( + const res = await http.get( `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window/_find` ); return res.data.map((mw) => transformMaintenanceWindowResponse(mw)); diff --git a/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/find_maintenance_windows.test.ts b/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/find_maintenance_windows.test.ts index 8d06b335892b9..35c15ebb57c61 100644 --- a/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/find_maintenance_windows.test.ts +++ b/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/find_maintenance_windows.test.ts @@ -17,6 +17,7 @@ import { MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE, } from '../../../../../common'; import { getMockMaintenanceWindow } from '../../../../data/maintenance_window/test_helpers'; +import { findMaintenanceWindowsParamsSchema } from './schemas'; const savedObjectsClient = savedObjectsClientMock.create(); const uiSettings = uiSettingsServiceMock.createClient(); @@ -37,8 +38,47 @@ describe('MaintenanceWindowClient - find', () => { jest.useRealTimers(); }); + it('throws an error if page is string', async () => { + savedObjectsClient.find.mockResolvedValueOnce({ + saved_objects: [ + { + attributes: getMockMaintenanceWindow({ expirationDate: new Date().toISOString() }), + id: 'test-1', + }, + { + attributes: getMockMaintenanceWindow({ expirationDate: new Date().toISOString() }), + id: 'test-2', + }, + ], + page: 1, + per_page: 5, + } as unknown as SavedObjectsFindResponse); + + await expect( + // @ts-expect-error: testing validation of strings + findMaintenanceWindows(mockContext, { page: 'dfsd', perPage: 10 }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + '"Error validating find maintenance windows data - [page]: expected value of type [number] but got [string]"' + ); + }); + + it('throws an error if savedObjectsClient.find will throw an error', async () => { + jest.useFakeTimers().setSystemTime(new Date('2023-02-26T00:00:00.000Z')); + + savedObjectsClient.find.mockImplementation(() => { + throw new Error('something went wrong!'); + }); + + await expect( + findMaintenanceWindows(mockContext, { page: 1, perPage: 10 }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + '"Failed to find maintenance window, Error: Error: something went wrong!: something went wrong!"' + ); + }); + it('should find maintenance windows', async () => { jest.useFakeTimers().setSystemTime(new Date('2023-02-26T00:00:00.000Z')); + const spy = jest.spyOn(findMaintenanceWindowsParamsSchema, 'validate'); savedObjectsClient.find.mockResolvedValueOnce({ saved_objects: [ @@ -51,10 +91,13 @@ describe('MaintenanceWindowClient - find', () => { id: 'test-2', }, ], + page: 1, + per_page: 5, } as unknown as SavedObjectsFindResponse); - const result = await findMaintenanceWindows(mockContext); + const result = await findMaintenanceWindows(mockContext, {}); + expect(spy).toHaveBeenCalledWith({}); expect(savedObjectsClient.find).toHaveBeenLastCalledWith({ type: MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE, }); @@ -62,5 +105,7 @@ describe('MaintenanceWindowClient - find', () => { expect(result.data.length).toEqual(2); expect(result.data[0].id).toEqual('test-1'); expect(result.data[1].id).toEqual('test-2'); + expect(result.page).toEqual(1); + expect(result.perPage).toEqual(5); }); }); diff --git a/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/find_maintenance_windows.ts b/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/find_maintenance_windows.ts index fe0f279ea4073..5cb1e01c1f1a0 100644 --- a/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/find_maintenance_windows.ts +++ b/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/find_maintenance_windows.ts @@ -9,17 +9,35 @@ import Boom from '@hapi/boom'; import { MaintenanceWindowClientContext } from '../../../../../common'; import { transformMaintenanceWindowAttributesToMaintenanceWindow } from '../../transforms'; import { findMaintenanceWindowSo } from '../../../../data/maintenance_window'; -import type { FindMaintenanceWindowsResult } from './types'; +import type { FindMaintenanceWindowsResult, FindMaintenanceWindowsParams } from './types'; +import { findMaintenanceWindowsParamsSchema } from './schemas'; export async function findMaintenanceWindows( - context: MaintenanceWindowClientContext + context: MaintenanceWindowClientContext, + params?: FindMaintenanceWindowsParams ): Promise { const { savedObjectsClient, logger } = context; try { - const result = await findMaintenanceWindowSo({ savedObjectsClient }); + if (params) { + findMaintenanceWindowsParamsSchema.validate(params); + } + } catch (error) { + throw Boom.badRequest(`Error validating find maintenance windows data - ${error.message}`); + } + + try { + const result = await findMaintenanceWindowSo({ + savedObjectsClient, + ...(params + ? { savedObjectsFindOptions: { page: params.page, perPage: params.perPage } } + : {}), + }); return { + page: result.page, + perPage: result.per_page, + total: result.total, data: result.saved_objects.map((so) => transformMaintenanceWindowAttributesToMaintenanceWindow({ attributes: so.attributes, diff --git a/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/schemas/find_maintenance_window_params_schema.ts b/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/schemas/find_maintenance_window_params_schema.ts new file mode 100644 index 0000000000000..e874882450c26 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/schemas/find_maintenance_window_params_schema.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +export const findMaintenanceWindowsParamsSchema = schema.object({ + perPage: schema.maybe(schema.number()), + page: schema.maybe(schema.number()), +}); diff --git a/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/schemas/find_maintenance_windows_result_schema.ts b/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/schemas/find_maintenance_windows_result_schema.ts index 1bdc2f00219ae..49b03325faa33 100644 --- a/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/schemas/find_maintenance_windows_result_schema.ts +++ b/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/schemas/find_maintenance_windows_result_schema.ts @@ -9,5 +9,8 @@ import { schema } from '@kbn/config-schema'; import { maintenanceWindowSchema } from '../../../schemas'; export const findMaintenanceWindowsResultSchema = schema.object({ + page: schema.number(), + perPage: schema.number(), data: schema.arrayOf(maintenanceWindowSchema), + total: schema.number(), }); diff --git a/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/schemas/index.ts b/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/schemas/index.ts index 4b2f087c95505..4e6c55b08955f 100644 --- a/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/schemas/index.ts +++ b/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/schemas/index.ts @@ -6,3 +6,4 @@ */ export { findMaintenanceWindowsResultSchema } from './find_maintenance_windows_result_schema'; +export { findMaintenanceWindowsParamsSchema } from './find_maintenance_window_params_schema'; diff --git a/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/types/find_maintenance_window_params.ts b/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/types/find_maintenance_window_params.ts new file mode 100644 index 0000000000000..878d5168c7e55 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/types/find_maintenance_window_params.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { TypeOf } from '@kbn/config-schema'; +import { findMaintenanceWindowsParamsSchema } from '../schemas'; + +export type FindMaintenanceWindowsParams = TypeOf; diff --git a/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/types/index.ts b/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/types/index.ts index a5f00973bb82e..97472fc231ab6 100644 --- a/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/types/index.ts +++ b/x-pack/plugins/alerting/server/application/maintenance_window/methods/find/types/index.ts @@ -6,3 +6,4 @@ */ export type { FindMaintenanceWindowsResult } from './find_maintenance_window_result'; +export type { FindMaintenanceWindowsParams } from './find_maintenance_window_params'; diff --git a/x-pack/plugins/alerting/server/data/maintenance_window/methods/find_maintenance_window_so.ts b/x-pack/plugins/alerting/server/data/maintenance_window/methods/find_maintenance_window_so.ts index baaed546c88cb..d08a3c360cbb0 100644 --- a/x-pack/plugins/alerting/server/data/maintenance_window/methods/find_maintenance_window_so.ts +++ b/x-pack/plugins/alerting/server/data/maintenance_window/methods/find_maintenance_window_so.ts @@ -24,7 +24,7 @@ export const findMaintenanceWindowSo = ({ - ...savedObjectsFindOptions, + ...(savedObjectsFindOptions ? savedObjectsFindOptions : {}), type: MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE, }); }; diff --git a/x-pack/plugins/alerting/server/maintenance_window_client/maintenance_window_client.ts b/x-pack/plugins/alerting/server/maintenance_window_client/maintenance_window_client.ts index 2c5e6f417ff3d..dba7ae0800ede 100644 --- a/x-pack/plugins/alerting/server/maintenance_window_client/maintenance_window_client.ts +++ b/x-pack/plugins/alerting/server/maintenance_window_client/maintenance_window_client.ts @@ -13,7 +13,10 @@ import type { GetMaintenanceWindowParams } from '../application/maintenance_wind import { updateMaintenanceWindow } from '../application/maintenance_window/methods/update/update_maintenance_window'; import type { UpdateMaintenanceWindowParams } from '../application/maintenance_window/methods/update/types'; import { findMaintenanceWindows } from '../application/maintenance_window/methods/find/find_maintenance_windows'; -import type { FindMaintenanceWindowsResult } from '../application/maintenance_window/methods/find/types'; +import type { + FindMaintenanceWindowsResult, + FindMaintenanceWindowsParams, +} from '../application/maintenance_window/methods/find/types'; import { deleteMaintenanceWindow } from '../application/maintenance_window/methods/delete/delete_maintenance_window'; import type { DeleteMaintenanceWindowParams } from '../application/maintenance_window/methods/delete/types'; import { archiveMaintenanceWindow } from '../application/maintenance_window/methods/archive/archive_maintenance_window'; @@ -75,7 +78,8 @@ export class MaintenanceWindowClient { getMaintenanceWindow(this.context, params); public update = (params: UpdateMaintenanceWindowParams): Promise => updateMaintenanceWindow(this.context, params); - public find = (): Promise => findMaintenanceWindows(this.context); + public find = (params?: FindMaintenanceWindowsParams): Promise => + findMaintenanceWindows(this.context, params); public delete = (params: DeleteMaintenanceWindowParams): Promise<{}> => deleteMaintenanceWindow(this.context, params); public archive = (params: ArchiveMaintenanceWindowParams): Promise => diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/find_maintenance_windows_route.test.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/find_maintenance_windows_route.test.ts index 0a51513f4a08a..99c1cf7b23f6a 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/find_maintenance_windows_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/find_maintenance_windows_route.test.ts @@ -22,6 +22,9 @@ jest.mock('../../../../lib/license_api_access', () => ({ })); const mockMaintenanceWindows = { + page: 1, + perPage: 3, + total: 2, data: [ { ...getMockMaintenanceWindow(), @@ -67,11 +70,54 @@ describe('findMaintenanceWindowsRoute', () => { await handler(context, req, res); - expect(maintenanceWindowClient.find).toHaveBeenCalled(); + expect(maintenanceWindowClient.find).toHaveBeenCalledWith({}); expect(res.ok).toHaveBeenLastCalledWith({ body: { data: mockMaintenanceWindows.data.map((data) => rewriteMaintenanceWindowRes(data)), total: 2, + page: 1, + per_page: 3, + }, + }); + }); + + test('should find the maintenance windows with query', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + findMaintenanceWindowsRoute(router, licenseState); + + maintenanceWindowClient.find.mockResolvedValueOnce(mockMaintenanceWindows); + const [config, handler] = router.get.mock.calls[0]; + const [context, req, res] = mockHandlerArguments( + { maintenanceWindowClient }, + { + query: { + page: 1, + per_page: 3, + }, + } + ); + + expect(config.path).toEqual('/internal/alerting/rules/maintenance_window/_find'); + expect(config.options).toMatchInlineSnapshot(` + Object { + "access": "internal", + "tags": Array [ + "access:read-maintenance-window", + ], + } + `); + + await handler(context, req, res); + + expect(maintenanceWindowClient.find).toHaveBeenCalledWith({ page: 1, perPage: 3 }); + expect(res.ok).toHaveBeenLastCalledWith({ + body: { + data: mockMaintenanceWindows.data.map((data) => rewriteMaintenanceWindowRes(data)), + total: 2, + page: 1, + per_page: 3, }, }); }); diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/find_maintenance_windows_route.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/find_maintenance_windows_route.ts index 7a7fb13160252..1aa4653e3d8d3 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/find_maintenance_windows_route.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/find_maintenance_windows_route.ts @@ -15,7 +15,15 @@ import { import { MAINTENANCE_WINDOW_API_PRIVILEGES } from '../../../../../common'; import type { FindMaintenanceWindowsResult } from '../../../../application/maintenance_window/methods/find/types'; import type { FindMaintenanceWindowsResponseV1 } from '../../../../../common/routes/maintenance_window/apis/find'; -import { transformMaintenanceWindowToResponseV1 } from '../../transforms'; +import { + findMaintenanceWindowsRequestQuerySchemaV1, + findMaintenanceWindowsResponseBodySchemaV1, + type FindMaintenanceWindowsRequestQueryV1, +} from '../../../../../common/routes/maintenance_window/apis/find'; +import { + transformFindMaintenanceWindowParamsV1, + transformFindMaintenanceWindowResponseV1, +} from './transforms'; export const findMaintenanceWindowsRoute = ( router: IRouter, @@ -24,7 +32,23 @@ export const findMaintenanceWindowsRoute = ( router.get( { path: `${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/_find`, - validate: {}, + validate: { + request: { + query: findMaintenanceWindowsRequestQuerySchemaV1, + }, + response: { + 200: { + body: () => findMaintenanceWindowsResponseBodySchemaV1, + description: 'Indicates a successful call.', + }, + 400: { + description: 'Indicates an invalid schema or parameters.', + }, + 403: { + description: 'Indicates that this call is forbidden.', + }, + }, + }, options: { access: 'internal', tags: [`access:${MAINTENANCE_WINDOW_API_PRIVILEGES.READ_MAINTENANCE_WINDOW}`], @@ -34,20 +58,17 @@ export const findMaintenanceWindowsRoute = ( verifyAccessAndContext(licenseState, async function (context, req, res) { licenseState.ensureLicenseForMaintenanceWindow(); + const query: FindMaintenanceWindowsRequestQueryV1 = req.query || {}; const maintenanceWindowClient = (await context.alerting).getMaintenanceWindowClient(); - const result: FindMaintenanceWindowsResult = await maintenanceWindowClient.find(); - - const response: FindMaintenanceWindowsResponseV1 = { - body: { - data: result.data.map((maintenanceWindow) => - transformMaintenanceWindowToResponseV1(maintenanceWindow) - ), - total: result.data.length, - }, - }; + const options = transformFindMaintenanceWindowParamsV1(query); + const findResult: FindMaintenanceWindowsResult = await maintenanceWindowClient.find( + options + ); + const responseBody: FindMaintenanceWindowsResponseV1 = + transformFindMaintenanceWindowResponseV1(findResult); - return res.ok(response); + return res.ok({ body: responseBody }); }) ) ); diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/transforms/index.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/transforms/index.ts new file mode 100644 index 0000000000000..43d428f9dd47a --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/transforms/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { transformFindMaintenanceWindowParams } from './transform_find_maintenance_window_params/latest'; +export { transformFindMaintenanceWindowResponse } from './transform_find_maintenance_window_to_response/latest'; + +export { transformFindMaintenanceWindowParams as transformFindMaintenanceWindowParamsV1 } from './transform_find_maintenance_window_params/v1'; +export { transformFindMaintenanceWindowResponse as transformFindMaintenanceWindowResponseV1 } from './transform_find_maintenance_window_to_response/v1'; diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/transforms/transform_find_maintenance_window_params/latest.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/transforms/transform_find_maintenance_window_params/latest.ts new file mode 100644 index 0000000000000..25300c97a6d2e --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/transforms/transform_find_maintenance_window_params/latest.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './v1'; diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/transforms/transform_find_maintenance_window_params/v1.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/transforms/transform_find_maintenance_window_params/v1.ts new file mode 100644 index 0000000000000..c59f5d189716e --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/transforms/transform_find_maintenance_window_params/v1.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FindMaintenanceWindowsRequestQuery } from '../../../../../../../common/routes/maintenance_window/apis/find'; +import { FindMaintenanceWindowsParams } from '../../../../../../application/maintenance_window/methods/find/types'; + +export const transformFindMaintenanceWindowParams = ( + params: FindMaintenanceWindowsRequestQuery +): FindMaintenanceWindowsParams => ({ + ...(params.page ? { page: params.page } : {}), + ...(params.per_page ? { perPage: params.per_page } : {}), +}); diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/transforms/transform_find_maintenance_window_to_response/latest.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/transforms/transform_find_maintenance_window_to_response/latest.ts new file mode 100644 index 0000000000000..25300c97a6d2e --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/transforms/transform_find_maintenance_window_to_response/latest.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './v1'; diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/transforms/transform_find_maintenance_window_to_response/v1.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/transforms/transform_find_maintenance_window_to_response/v1.ts new file mode 100644 index 0000000000000..9110914a998ca --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/transforms/transform_find_maintenance_window_to_response/v1.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { transformMaintenanceWindowToResponseV1 } from '../../../../transforms'; +import type { FindMaintenanceWindowsResponseV1 } from '../../../../../../../common/routes/maintenance_window/apis/find'; +import type { MaintenanceWindow } from '../../../../../../application/maintenance_window/types'; +import type { FindMaintenanceWindowsResult } from '../../../../../../application/maintenance_window/methods/find/types'; + +export const transformFindMaintenanceWindowResponse = ( + result: FindMaintenanceWindowsResult +): FindMaintenanceWindowsResponseV1 => { + return { + page: result.page, + per_page: result.perPage, + total: result.total, + data: result.data.map((maintenanceWindow: MaintenanceWindow) => + transformMaintenanceWindowToResponseV1(maintenanceWindow) + ), + }; +}; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/maintenance_window/find_maintenance_windows.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/maintenance_window/find_maintenance_windows.ts index 0efdfb72f7626..9c546ea6b5c27 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/maintenance_window/find_maintenance_windows.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/maintenance_window/find_maintenance_windows.ts @@ -90,6 +90,118 @@ export default function findMaintenanceWindowTests({ getService }: FtrProviderCo throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); } }); + + it('should handle find maintenance window request with pagination', async () => { + const { body: createdMaintenanceWindow1 } = await supertest + .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/maintenance_window`) + .set('kbn-xsrf', 'foo') + .send(createParams); + + const { body: createdMaintenanceWindow2 } = await supertest + .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/maintenance_window`) + .set('kbn-xsrf', 'foo') + .send({ ...createParams, title: 'test-maintenance-window2' }); + + objectRemover.add( + space.id, + createdMaintenanceWindow1.id, + 'rules/maintenance_window', + 'alerting', + true + ); + objectRemover.add( + space.id, + createdMaintenanceWindow2.id, + 'rules/maintenance_window', + 'alerting', + true + ); + + const response = await supertestWithoutAuth + .get( + `${getUrlPrefix( + space.id + )}/internal/alerting/rules/maintenance_window/_find?page=1&per_page=1` + ) + .set('kbn-xsrf', 'foo') + .auth(user.username, user.password) + .send({}); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + case 'space_1_all_with_restricted_fixture at space1': + case 'space_1_all_alerts_none_actions at space1': + expect(response.statusCode).to.eql(403); + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Forbidden', + statusCode: 403, + }); + break; + case 'global_read at space1': + case 'superuser at space1': + case 'space_1_all at space1': + expect(response.body.total).to.eql(2); + expect(response.statusCode).to.eql(200); + expect(response.body.data[0].id).to.eql(createdMaintenanceWindow1.id); + expect(response.body.data[0].title).to.eql('test-maintenance-window'); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('throw an error for find maintenance window request with pagination if docs count more 10k', async () => { + const { body: createdMaintenanceWindow1 } = await supertest + .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/maintenance_window`) + .set('kbn-xsrf', 'foo') + .send(createParams); + + objectRemover.add( + space.id, + createdMaintenanceWindow1.id, + 'rules/maintenance_window', + 'alerting', + true + ); + + const response = await supertestWithoutAuth + .get( + `${getUrlPrefix( + space.id + )}/internal/alerting/rules/maintenance_window/_find?page=101&per_page=100` + ) + .set('kbn-xsrf', 'foo') + .auth(user.username, user.password) + .send({}); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + case 'space_1_all_with_restricted_fixture at space1': + case 'space_1_all_alerts_none_actions at space1': + expect(response.statusCode).to.eql(403); + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Forbidden', + statusCode: 403, + }); + break; + case 'global_read at space1': + case 'superuser at space1': + case 'space_1_all at space1': + expect(response.body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: + '[request query]: The number of documents is too high. Paginating through more than 10000 documents is not possible.', + }); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); }); } });