diff --git a/x-pack/plugins/apm/common/apm_api/parse_endpoint.ts b/x-pack/plugins/apm/common/apm_api/parse_endpoint.ts new file mode 100644 index 0000000000000..fb7ef6d36ce25 --- /dev/null +++ b/x-pack/plugins/apm/common/apm_api/parse_endpoint.ts @@ -0,0 +1,32 @@ +/* + * 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. + */ + +type Method = 'get' | 'post' | 'put' | 'delete'; + +export function parseEndpoint( + endpoint: string, + pathParams: Record = {} +) { + const [method, rawPathname] = endpoint.split(' '); + + // replace template variables with path params + const pathname = Object.keys(pathParams).reduce((acc, paramName) => { + return acc.replace(`{${paramName}}`, pathParams[paramName]); + }, rawPathname); + + return { method: parseMethod(method), pathname }; +} + +export function parseMethod(method: string) { + const res = method.trim().toLowerCase() as Method; + + if (!['get', 'post', 'put', 'delete'].includes(res)) { + throw new Error('Endpoint was not prefixed with a valid HTTP method'); + } + + return res; +} diff --git a/x-pack/plugins/apm/public/services/rest/createCallApmApi.ts b/x-pack/plugins/apm/public/services/rest/createCallApmApi.ts index 8bea16947d16e..b0cce3296fe21 100644 --- a/x-pack/plugins/apm/public/services/rest/createCallApmApi.ts +++ b/x-pack/plugins/apm/public/services/rest/createCallApmApi.ts @@ -6,12 +6,13 @@ */ import { CoreSetup, CoreStart } from 'kibana/public'; +import { parseEndpoint } from '../../../common/apm_api/parse_endpoint'; import { FetchOptions } from '../../../common/fetch_options'; import { callApi } from './callApi'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { APMAPI } from '../../../server/routes/create_apm_api'; +import type { APMAPI } from '../../../server/routes/create_apm_api'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { Client } from '../../../server/routes/typings'; +import type { Client } from '../../../server/routes/typings'; export type APMClient = Client; export type AutoAbortedAPMClient = Client; @@ -24,8 +25,8 @@ export type APMClientOptions = Omit< signal: AbortSignal | null; params?: { body?: any; - query?: any; - path?: any; + query?: Record; + path?: Record; }; }; @@ -37,20 +38,15 @@ export let callApmApi: APMClient = () => { export function createCallApmApi(core: CoreStart | CoreSetup) { callApmApi = ((options: APMClientOptions) => { - const { endpoint, params = {}, ...opts } = options; - const path = (params.path || {}) as Record; - const [method, pathname] = endpoint.split(' '); - - const formattedPathname = Object.keys(path).reduce((acc, paramName) => { - return acc.replace(`{${paramName}}`, path[paramName]); - }, pathname); + const { endpoint, params, ...opts } = options; + const { method, pathname } = parseEndpoint(endpoint, params?.path); return callApi(core, { ...opts, method, - pathname: formattedPathname, - body: params.body, - query: params.query, + pathname, + body: params?.body, + query: params?.query, }); }) as APMClient; } diff --git a/x-pack/plugins/apm/server/routes/create_api/index.ts b/x-pack/plugins/apm/server/routes/create_api/index.ts index c8e7e0c9db693..b225d01d6d62e 100644 --- a/x-pack/plugins/apm/server/routes/create_api/index.ts +++ b/x-pack/plugins/apm/server/routes/create_api/index.ts @@ -14,14 +14,15 @@ import { isLeft } from 'fp-ts/lib/Either'; import { KibanaRequest, RouteRegistrar } from 'src/core/server'; import { RequestAbortedError } from '@elastic/elasticsearch/lib/errors'; import agent from 'elastic-apm-node'; +import { parseMethod } from '../../../common/apm_api/parse_endpoint'; import { merge } from '../../../common/runtime_types/merge'; import { strictKeysRt } from '../../../common/runtime_types/strict_keys_rt'; import { APMConfig } from '../..'; -import { ServerAPI } from '../typings'; +import { RouteParamsRT, ServerAPI } from '../typings'; import { jsonRt } from '../../../common/runtime_types/json_rt'; import type { ApmPluginRequestHandlerContext } from '../typings'; -const debugRt = t.exact( +const inspectRt = t.exact( t.partial({ query: t.exact(t.partial({ _inspect: jsonRt.pipe(t.boolean) })), }) @@ -71,24 +72,10 @@ export function createApi() { const { params, endpoint, options, handler } = route; const [method, path] = endpoint.split(' '); - - const typedRouterMethod = method.trim().toLowerCase() as - | 'get' - | 'post' - | 'put' - | 'delete'; - - if (!['get', 'post', 'put', 'delete'].includes(typedRouterMethod)) { - throw new Error( - "Couldn't register route, as endpoint was not prefixed with a valid HTTP method" - ); - } + const typedRouterMethod = parseMethod(method); // For all runtime types with props, we create an exact // version that will strip all keys that are unvalidated. - - const paramsRt = params ? merge([params, debugRt]) : debugRt; - const anyObject = schema.object({}, { unknowns: 'allow' }); (router[typedRouterMethod] as RouteRegistrar< @@ -119,44 +106,24 @@ export function createApi() { inspectableEsQueriesMap.set(request, []); try { - const paramMap = pickBy( - { - path: request.params, - body: request.body, - query: { - _inspect: 'false', - ...request.query, - }, - }, - isNotEmpty - ); - - const result = strictKeysRt(paramsRt).decode(paramMap); - - if (isLeft(result)) { - throw Boom.badRequest(PathReporter.report(result)[0]); - } + const validParams = validateParams(request, params); const data = await handler({ request, context: { ...context, plugins, - // Only return values for parameters that have runtime types, - // but always include query as _inspect is always set even if - // it's not defined in the route. - params: mergeLodash( - { query: { _inspect: false } }, - pickBy(result.right, isNotEmpty) - ), + params: validParams, config, logger, }, }); - const body = { - ...data, - _inspectableEsQueries: inspectableEsQueriesMap.get(request), - }; + const body = { ...data }; + if (validParams.query._inspect) { + body._inspectableEsQueries = inspectableEsQueriesMap.get( + request + ); + } // cleanup inspectableEsQueriesMap.delete(request); @@ -192,3 +159,36 @@ export function createApi() { return api; } + +function validateParams( + request: KibanaRequest, + params: RouteParamsRT | undefined +) { + const paramsRt = params ? merge([params, inspectRt]) : inspectRt; + const paramMap = pickBy( + { + path: request.params, + body: request.body, + query: { + _inspect: 'false', + // @ts-ignore + ...request.query, + }, + }, + isNotEmpty + ); + + const result = strictKeysRt(paramsRt).decode(paramMap); + + if (isLeft(result)) { + throw Boom.badRequest(PathReporter.report(result)[0]); + } + + // Only return values for parameters that have runtime types, + // but always include query as _inspect is always set even if + // it's not defined in the route. + return mergeLodash( + { query: { _inspect: false } }, + pickBy(result.right, isNotEmpty) + ); +} diff --git a/x-pack/plugins/apm/server/routes/observability_overview.ts b/x-pack/plugins/apm/server/routes/observability_overview.ts index fe8d8123fc85f..b9c0a76b6fb90 100644 --- a/x-pack/plugins/apm/server/routes/observability_overview.ts +++ b/x-pack/plugins/apm/server/routes/observability_overview.ts @@ -20,7 +20,8 @@ export const observabilityOverviewHasDataRoute = createRoute({ options: { tags: ['access:apm'] }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); - return { hasData: await getHasData({ setup }) }; + const res = await getHasData({ setup }); + return { hasData: res }; }, }); diff --git a/x-pack/plugins/apm/server/routes/typings.ts b/x-pack/plugins/apm/server/routes/typings.ts index b0c3bc80ba464..3fd46673863c5 100644 --- a/x-pack/plugins/apm/server/routes/typings.ts +++ b/x-pack/plugins/apm/server/routes/typings.ts @@ -134,6 +134,13 @@ type MaybeOptional }> = RequiredKeys< ? { params?: T['params'] } : { params: T['params'] }; +export type MaybeParams< + TRouteState, + TEndpoint extends keyof TRouteState & string +> = TRouteState[TEndpoint] extends { params: t.Any } + ? MaybeOptional<{ params: t.OutputOf }> + : {}; + export type Client< TRouteState, TOptions extends { abortable: boolean } = { abortable: true } @@ -144,9 +151,7 @@ export type Client< > & { forceCache?: boolean; endpoint: TEndpoint; - } & (TRouteState[TEndpoint] extends { params: t.Any } - ? MaybeOptional<{ params: t.OutputOf }> - : {}) & + } & MaybeParams & (TOptions extends { abortable: true } ? { signal: AbortSignal | null } : {}) ) => Promise< TRouteState[TEndpoint] extends { ret: any } diff --git a/x-pack/test/apm_api_integration/common/apm_api_supertest.ts b/x-pack/test/apm_api_integration/common/apm_api_supertest.ts new file mode 100644 index 0000000000000..76eab7ab85cf1 --- /dev/null +++ b/x-pack/test/apm_api_integration/common/apm_api_supertest.ts @@ -0,0 +1,50 @@ +/* + * 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 { format } from 'url'; +import supertest from 'supertest'; +import { MaybeParams } from '../../../plugins/apm/server/routes/typings'; +import { parseEndpoint } from '../../../plugins/apm/common/apm_api/parse_endpoint'; +import { APMAPI } from '../../../plugins/apm/server/routes/create_apm_api'; +import type { APIReturnType } from '../../../plugins/apm/public/services/rest/createCallApmApi'; + +export function createApmApiSupertest(st: supertest.SuperTest) { + return async ( + options: { + endpoint: TPath; + } & MaybeParams + ): Promise<{ + status: number; + body: APIReturnType; + }> => { + const { endpoint } = options; + + // @ts-expect-error + const params = 'params' in options ? options.params : {}; + + const { method, pathname } = parseEndpoint(endpoint, params?.path); + const url = format({ pathname, query: params?.query }); + + const res = params.body + ? await st[method](url).send(params.body).set('kbn-xsrf', 'foo') + : await st[method](url).set('kbn-xsrf', 'foo'); + + // supertest doesn't throw on http errors + if (res.status !== 200) { + const e = new Error( + `Unhandled ApmApiSupertest error. Status: "${ + res.status + }". Endpoint: "${endpoint}". ${JSON.stringify(res.body)}` + ); + // @ts-expect-error + e.res = res; + throw e; + } + + return res; + }; +} diff --git a/x-pack/test/apm_api_integration/common/config.ts b/x-pack/test/apm_api_integration/common/config.ts index adf6fcfbe9818..04ce83323ee66 100644 --- a/x-pack/test/apm_api_integration/common/config.ts +++ b/x-pack/test/apm_api_integration/common/config.ts @@ -6,7 +6,7 @@ */ import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; -import supertestAsPromised from 'supertest-as-promised'; +import supertest from 'supertest'; import { format, UrlObject } from 'url'; import path from 'path'; import { InheritedFtrProviderContext, InheritedServices } from './ftr_provider_context'; @@ -33,7 +33,7 @@ const supertestAsApmUser = (kibanaServer: UrlObject, apmUser: ApmUser) => async auth: `${apmUser}:${APM_TEST_PASSWORD}`, }); - return supertestAsPromised(url); + return supertest(url); }; export function createTestConfig(config: Config) { diff --git a/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts b/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts index 58da2e2b0df5b..3712b49ce1696 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts @@ -6,66 +6,112 @@ */ import expect from '@kbn/expect'; -import { format } from 'url'; +import { createApmApiSupertest } from '../../common/apm_api_supertest'; import archives from '../../common/fixtures/es_archiver/archives_metadata'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const apmApiSupertest = createApmApiSupertest(getService('supertest')); const archiveName = 'apm_8.0.0'; const { end } = archives[archiveName]; const start = new Date(Date.parse(end) - 600000).toISOString(); - const apis = [ - { - pathname: '/api/apm/alerts/chart_preview/transaction_error_rate', - params: { transactionType: 'request' }, - }, - { pathname: '/api/apm/alerts/chart_preview/transaction_error_count', params: {} }, - { - pathname: '/api/apm/alerts/chart_preview/transaction_duration', - params: { transactionType: 'request' }, - }, - ]; - - apis.forEach((api) => { - const url = format({ - pathname: api.pathname, + const getOptions = () => ({ + params: { query: { start, end, serviceName: 'opbeans-java', - ...api.params, + transactionType: 'request' as string | undefined, }, + }, + }); + + registry.when(`without data loaded`, { config: 'basic', archives: [] }, () => { + it('transaction_error_rate (without data)', async () => { + const options = getOptions(); + const response = await apmApiSupertest({ + endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_rate', + ...options, + }); + + expect(response.status).to.be(200); + expect(response.body.errorRateChartPreview).to.eql([]); }); - registry.when( - `GET ${api.pathname} without data loaded`, - { config: 'basic', archives: [] }, - () => { - it('handles the empty state', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect(response.body).to.eql([]); - }); - } - ); - - registry.when( - `GET ${api.pathname} with data loaded`, - { config: 'basic', archives: [archiveName] }, - () => { - it('returns the correct data', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect( - response.body.some((item: { x: number; y: number | null }) => item.x && item.y) - ).to.equal(true); - }); - } - ); + it('transaction_error_count (without data)', async () => { + const options = getOptions(); + options.params.query.transactionType = undefined; + + const response = await apmApiSupertest({ + endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_count', + ...options, + }); + + expect(response.status).to.be(200); + expect(response.body.errorCountChartPreview).to.eql([]); + }); + + it('transaction_duration (without data)', async () => { + const options = getOptions(); + + const response = await apmApiSupertest({ + endpoint: 'GET /api/apm/alerts/chart_preview/transaction_duration', + ...options, + }); + + expect(response.status).to.be(200); + expect(response.body.latencyChartPreview).to.eql([]); + }); + }); + + registry.when(`with data loaded`, { config: 'basic', archives: [archiveName] }, () => { + it('transaction_error_rate (with data)', async () => { + const options = getOptions(); + const response = await apmApiSupertest({ + endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_rate', + ...options, + }); + + expect(response.status).to.be(200); + expect( + response.body.errorRateChartPreview.some( + (item: { x: number; y: number | null }) => item.x && item.y + ) + ).to.equal(true); + }); + + it('transaction_error_count (with data)', async () => { + const options = getOptions(); + options.params.query.transactionType = undefined; + + const response = await apmApiSupertest({ + endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_count', + ...options, + }); + + expect(response.status).to.be(200); + expect( + response.body.errorCountChartPreview.some( + (item: { x: number; y: number | null }) => item.x && item.y + ) + ).to.equal(true); + }); + + it('transaction_duration (with data)', async () => { + const options = getOptions(); + const response = await apmApiSupertest({ + ...options, + endpoint: 'GET /api/apm/alerts/chart_preview/transaction_duration', + }); + + expect(response.status).to.be(200); + expect( + response.body.latencyChartPreview.some( + (item: { x: number; y: number | null }) => item.x && item.y + ) + ).to.equal(true); + }); }); } diff --git a/x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_load_dist.snap b/x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_load_dist.snap index e21069ba2f0a6..9e4a708fae304 100644 --- a/x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_load_dist.snap +++ b/x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_load_dist.snap @@ -2,464 +2,10 @@ exports[`APM API tests trial 8.0.0,rum_8.0.0 UX page load dist with data returns page load distribution 1`] = ` Object { - "maxDuration": 54.46, - "minDuration": 0, - "pageLoadDistribution": Array [ - Object { - "x": 0, - "y": 0, - }, - Object { - "x": 0.5, - "y": 0, - }, - Object { - "x": 1, - "y": 0, - }, - Object { - "x": 1.5, - "y": 0, - }, - Object { - "x": 2, - "y": 0, - }, - Object { - "x": 2.5, - "y": 0, - }, - Object { - "x": 3, - "y": 16.6666666666667, - }, - Object { - "x": 3.5, - "y": 0, - }, - Object { - "x": 4, - "y": 0, - }, - Object { - "x": 4.5, - "y": 0, - }, - Object { - "x": 5, - "y": 50, - }, - Object { - "x": 5.5, - "y": 0, - }, - Object { - "x": 6, - "y": 0, - }, - Object { - "x": 6.5, - "y": 0, - }, - Object { - "x": 7, - "y": 0, - }, - Object { - "x": 7.5, - "y": 0, - }, - Object { - "x": 8, - "y": 0, - }, - Object { - "x": 8.5, - "y": 0, - }, - Object { - "x": 9, - "y": 0, - }, - Object { - "x": 9.5, - "y": 0, - }, - Object { - "x": 10, - "y": 0, - }, - Object { - "x": 10.5, - "y": 0, - }, - Object { - "x": 11, - "y": 0, - }, - Object { - "x": 11.5, - "y": 0, - }, - Object { - "x": 12, - "y": 0, - }, - Object { - "x": 12.5, - "y": 0, - }, - Object { - "x": 13, - "y": 0, - }, - Object { - "x": 13.5, - "y": 0, - }, - Object { - "x": 14, - "y": 0, - }, - Object { - "x": 14.5, - "y": 0, - }, - Object { - "x": 15, - "y": 0, - }, - Object { - "x": 15.5, - "y": 0, - }, - Object { - "x": 16, - "y": 0, - }, - Object { - "x": 16.5, - "y": 0, - }, - Object { - "x": 17, - "y": 0, - }, - Object { - "x": 17.5, - "y": 0, - }, - Object { - "x": 18, - "y": 0, - }, - Object { - "x": 18.5, - "y": 0, - }, - Object { - "x": 19, - "y": 0, - }, - Object { - "x": 19.5, - "y": 0, - }, - Object { - "x": 20, - "y": 0, - }, - Object { - "x": 20.5, - "y": 0, - }, - Object { - "x": 21, - "y": 0, - }, - Object { - "x": 21.5, - "y": 0, - }, - Object { - "x": 22, - "y": 0, - }, - Object { - "x": 22.5, - "y": 0, - }, - Object { - "x": 23, - "y": 0, - }, - Object { - "x": 23.5, - "y": 0, - }, - Object { - "x": 24, - "y": 0, - }, - Object { - "x": 24.5, - "y": 0, - }, - Object { - "x": 25, - "y": 0, - }, - Object { - "x": 25.5, - "y": 0, - }, - Object { - "x": 26, - "y": 0, - }, - Object { - "x": 26.5, - "y": 0, - }, - Object { - "x": 27, - "y": 0, - }, - Object { - "x": 27.5, - "y": 0, - }, - Object { - "x": 28, - "y": 0, - }, - Object { - "x": 28.5, - "y": 0, - }, - Object { - "x": 29, - "y": 0, - }, - Object { - "x": 29.5, - "y": 0, - }, - Object { - "x": 30, - "y": 0, - }, - Object { - "x": 30.5, - "y": 0, - }, - Object { - "x": 31, - "y": 0, - }, - Object { - "x": 31.5, - "y": 0, - }, - Object { - "x": 32, - "y": 0, - }, - Object { - "x": 32.5, - "y": 0, - }, - Object { - "x": 33, - "y": 0, - }, - Object { - "x": 33.5, - "y": 0, - }, - Object { - "x": 34, - "y": 0, - }, - Object { - "x": 34.5, - "y": 0, - }, - Object { - "x": 35, - "y": 0, - }, - Object { - "x": 35.5, - "y": 0, - }, - Object { - "x": 36, - "y": 0, - }, - Object { - "x": 36.5, - "y": 0, - }, - Object { - "x": 37, - "y": 0, - }, - Object { - "x": 37.5, - "y": 16.6666666666667, - }, - Object { - "x": 38, - "y": 0, - }, - Object { - "x": 38.5, - "y": 0, - }, - Object { - "x": 39, - "y": 0, - }, - Object { - "x": 39.5, - "y": 0, - }, - Object { - "x": 40, - "y": 0, - }, - Object { - "x": 40.5, - "y": 0, - }, - Object { - "x": 41, - "y": 0, - }, - Object { - "x": 41.5, - "y": 0, - }, - Object { - "x": 42, - "y": 0, - }, - Object { - "x": 42.5, - "y": 0, - }, - Object { - "x": 43, - "y": 0, - }, - Object { - "x": 43.5, - "y": 0, - }, - Object { - "x": 44, - "y": 0, - }, - Object { - "x": 44.5, - "y": 0, - }, - Object { - "x": 45, - "y": 0, - }, - Object { - "x": 45.5, - "y": 0, - }, - Object { - "x": 46, - "y": 0, - }, - Object { - "x": 46.5, - "y": 0, - }, - Object { - "x": 47, - "y": 0, - }, - Object { - "x": 47.5, - "y": 0, - }, - Object { - "x": 48, - "y": 0, - }, - Object { - "x": 48.5, - "y": 0, - }, - Object { - "x": 49, - "y": 0, - }, - Object { - "x": 49.5, - "y": 0, - }, - Object { - "x": 50, - "y": 0, - }, - Object { - "x": 50.5, - "y": 0, - }, - Object { - "x": 51, - "y": 0, - }, - Object { - "x": 51.5, - "y": 0, - }, - Object { - "x": 52, - "y": 0, - }, - Object { - "x": 52.5, - "y": 0, - }, - Object { - "x": 53, - "y": 0, - }, - Object { - "x": 53.5, - "y": 0, - }, - Object { - "x": 54, - "y": 0, - }, - Object { - "x": 54.5, - "y": 16.6666666666667, - }, - ], - "percentiles": Object { - "50.0": 4.88, - "75.0": 37.09, - "90.0": 37.09, - "95.0": 54.46, - "99.0": 54.46, - }, -} -`; - -exports[`APM API tests trial 8.0.0,rum_8.0.0 UX page load dist with data returns page load distribution with breakdown 1`] = ` -Array [ - Object { - "data": Array [ + "pageLoadDistribution": Object { + "maxDuration": 54.46, + "minDuration": 0, + "pageLoadDistribution": Array [ Object { "x": 0, "y": 0, @@ -486,7 +32,7 @@ Array [ }, Object { "x": 3, - "y": 25, + "y": 16.6666666666667, }, Object { "x": 3.5, @@ -502,7 +48,7 @@ Array [ }, Object { "x": 5, - "y": 25, + "y": 50, }, Object { "x": 5.5, @@ -762,63 +308,525 @@ Array [ }, Object { "x": 37.5, - "y": 25, + "y": 16.6666666666667, }, - ], - "name": "Chrome", - }, - Object { - "data": Array [ Object { - "x": 0, + "x": 38, "y": 0, }, Object { - "x": 0.5, + "x": 38.5, "y": 0, }, Object { - "x": 1, + "x": 39, "y": 0, }, Object { - "x": 1.5, + "x": 39.5, "y": 0, }, Object { - "x": 2, + "x": 40, "y": 0, }, Object { - "x": 2.5, + "x": 40.5, "y": 0, }, Object { - "x": 3, + "x": 41, "y": 0, }, Object { - "x": 3.5, + "x": 41.5, "y": 0, }, Object { - "x": 4, + "x": 42, "y": 0, }, Object { - "x": 4.5, + "x": 42.5, "y": 0, }, Object { - "x": 5, - "y": 100, + "x": 43, + "y": 0, + }, + Object { + "x": 43.5, + "y": 0, + }, + Object { + "x": 44, + "y": 0, + }, + Object { + "x": 44.5, + "y": 0, + }, + Object { + "x": 45, + "y": 0, + }, + Object { + "x": 45.5, + "y": 0, + }, + Object { + "x": 46, + "y": 0, + }, + Object { + "x": 46.5, + "y": 0, + }, + Object { + "x": 47, + "y": 0, + }, + Object { + "x": 47.5, + "y": 0, + }, + Object { + "x": 48, + "y": 0, + }, + Object { + "x": 48.5, + "y": 0, + }, + Object { + "x": 49, + "y": 0, + }, + Object { + "x": 49.5, + "y": 0, + }, + Object { + "x": 50, + "y": 0, + }, + Object { + "x": 50.5, + "y": 0, + }, + Object { + "x": 51, + "y": 0, + }, + Object { + "x": 51.5, + "y": 0, + }, + Object { + "x": 52, + "y": 0, + }, + Object { + "x": 52.5, + "y": 0, + }, + Object { + "x": 53, + "y": 0, + }, + Object { + "x": 53.5, + "y": 0, + }, + Object { + "x": 54, + "y": 0, + }, + Object { + "x": 54.5, + "y": 16.6666666666667, }, ], - "name": "Chrome Mobile", + "percentiles": Object { + "50.0": 4.88, + "75.0": 37.09, + "90.0": 37.09, + "95.0": 54.46, + "99.0": 54.46, + }, }, -] +} `; -exports[`APM API tests trial no data UX page load dist without data returns empty list 1`] = `Object {}`; +exports[`APM API tests trial 8.0.0,rum_8.0.0 UX page load dist with data returns page load distribution with breakdown 1`] = ` +Object { + "pageLoadDistBreakdown": Array [ + Object { + "data": Array [ + Object { + "x": 0, + "y": 0, + }, + Object { + "x": 0.5, + "y": 0, + }, + Object { + "x": 1, + "y": 0, + }, + Object { + "x": 1.5, + "y": 0, + }, + Object { + "x": 2, + "y": 0, + }, + Object { + "x": 2.5, + "y": 0, + }, + Object { + "x": 3, + "y": 25, + }, + Object { + "x": 3.5, + "y": 0, + }, + Object { + "x": 4, + "y": 0, + }, + Object { + "x": 4.5, + "y": 0, + }, + Object { + "x": 5, + "y": 25, + }, + Object { + "x": 5.5, + "y": 0, + }, + Object { + "x": 6, + "y": 0, + }, + Object { + "x": 6.5, + "y": 0, + }, + Object { + "x": 7, + "y": 0, + }, + Object { + "x": 7.5, + "y": 0, + }, + Object { + "x": 8, + "y": 0, + }, + Object { + "x": 8.5, + "y": 0, + }, + Object { + "x": 9, + "y": 0, + }, + Object { + "x": 9.5, + "y": 0, + }, + Object { + "x": 10, + "y": 0, + }, + Object { + "x": 10.5, + "y": 0, + }, + Object { + "x": 11, + "y": 0, + }, + Object { + "x": 11.5, + "y": 0, + }, + Object { + "x": 12, + "y": 0, + }, + Object { + "x": 12.5, + "y": 0, + }, + Object { + "x": 13, + "y": 0, + }, + Object { + "x": 13.5, + "y": 0, + }, + Object { + "x": 14, + "y": 0, + }, + Object { + "x": 14.5, + "y": 0, + }, + Object { + "x": 15, + "y": 0, + }, + Object { + "x": 15.5, + "y": 0, + }, + Object { + "x": 16, + "y": 0, + }, + Object { + "x": 16.5, + "y": 0, + }, + Object { + "x": 17, + "y": 0, + }, + Object { + "x": 17.5, + "y": 0, + }, + Object { + "x": 18, + "y": 0, + }, + Object { + "x": 18.5, + "y": 0, + }, + Object { + "x": 19, + "y": 0, + }, + Object { + "x": 19.5, + "y": 0, + }, + Object { + "x": 20, + "y": 0, + }, + Object { + "x": 20.5, + "y": 0, + }, + Object { + "x": 21, + "y": 0, + }, + Object { + "x": 21.5, + "y": 0, + }, + Object { + "x": 22, + "y": 0, + }, + Object { + "x": 22.5, + "y": 0, + }, + Object { + "x": 23, + "y": 0, + }, + Object { + "x": 23.5, + "y": 0, + }, + Object { + "x": 24, + "y": 0, + }, + Object { + "x": 24.5, + "y": 0, + }, + Object { + "x": 25, + "y": 0, + }, + Object { + "x": 25.5, + "y": 0, + }, + Object { + "x": 26, + "y": 0, + }, + Object { + "x": 26.5, + "y": 0, + }, + Object { + "x": 27, + "y": 0, + }, + Object { + "x": 27.5, + "y": 0, + }, + Object { + "x": 28, + "y": 0, + }, + Object { + "x": 28.5, + "y": 0, + }, + Object { + "x": 29, + "y": 0, + }, + Object { + "x": 29.5, + "y": 0, + }, + Object { + "x": 30, + "y": 0, + }, + Object { + "x": 30.5, + "y": 0, + }, + Object { + "x": 31, + "y": 0, + }, + Object { + "x": 31.5, + "y": 0, + }, + Object { + "x": 32, + "y": 0, + }, + Object { + "x": 32.5, + "y": 0, + }, + Object { + "x": 33, + "y": 0, + }, + Object { + "x": 33.5, + "y": 0, + }, + Object { + "x": 34, + "y": 0, + }, + Object { + "x": 34.5, + "y": 0, + }, + Object { + "x": 35, + "y": 0, + }, + Object { + "x": 35.5, + "y": 0, + }, + Object { + "x": 36, + "y": 0, + }, + Object { + "x": 36.5, + "y": 0, + }, + Object { + "x": 37, + "y": 0, + }, + Object { + "x": 37.5, + "y": 25, + }, + ], + "name": "Chrome", + }, + Object { + "data": Array [ + Object { + "x": 0, + "y": 0, + }, + Object { + "x": 0.5, + "y": 0, + }, + Object { + "x": 1, + "y": 0, + }, + Object { + "x": 1.5, + "y": 0, + }, + Object { + "x": 2, + "y": 0, + }, + Object { + "x": 2.5, + "y": 0, + }, + Object { + "x": 3, + "y": 0, + }, + Object { + "x": 3.5, + "y": 0, + }, + Object { + "x": 4, + "y": 0, + }, + Object { + "x": 4.5, + "y": 0, + }, + Object { + "x": 5, + "y": 100, + }, + ], + "name": "Chrome Mobile", + }, + ], +} +`; + +exports[`APM API tests trial no data UX page load dist without data returns empty list 1`] = ` +Object { + "pageLoadDistribution": null, +} +`; exports[`APM API tests trial no data UX page load dist without data returns empty list with breakdowns 1`] = `Object {}`; diff --git a/x-pack/test/apm_api_integration/tests/csm/csm_services.ts b/x-pack/test/apm_api_integration/tests/csm/csm_services.ts index c0f92f5f2accf..57018b5012aa2 100644 --- a/x-pack/test/apm_api_integration/tests/csm/csm_services.ts +++ b/x-pack/test/apm_api_integration/tests/csm/csm_services.ts @@ -19,7 +19,7 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) ); expect(response.status).to.be(200); - expect(response.body).to.eql([]); + expect(response.body.rumServices).to.eql([]); }); }); @@ -34,7 +34,7 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) expect(response.status).to.be(200); - expectSnapshot(response.body).toMatchInline(`Array []`); + expectSnapshot(response.body.rumServices).toMatchInline(`Array []`); }); } ); diff --git a/x-pack/test/apm_api_integration/tests/feature_controls.ts b/x-pack/test/apm_api_integration/tests/feature_controls.ts index e82b14d6cb7e6..074860667a112 100644 --- a/x-pack/test/apm_api_integration/tests/feature_controls.ts +++ b/x-pack/test/apm_api_integration/tests/feature_controls.ts @@ -42,9 +42,9 @@ export default function featureControlsTests({ getService }: FtrProviderContext) } const endpoints: Endpoint[] = [ { - // this doubles as a smoke test for the _debug query parameter + // this doubles as a smoke test for the _inspect query parameter req: { - url: `/api/apm/services/foo/errors?start=${start}&end=${end}&_debug=true`, + url: `/api/apm/services/foo/errors?start=${start}&end=${end}&_inspect=true`, }, expectForbidden: expect403, expectResponse: expect200, diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts index 3884b3ae750a5..0704d82e67bbd 100644 --- a/x-pack/test/apm_api_integration/tests/index.ts +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -13,60 +13,177 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte describe('APM API tests', function () { this.tags('ciGroup1'); - loadTestFile(require.resolve('./alerts/chart_preview')); - loadTestFile(require.resolve('./correlations/slow_transactions')); + describe('alerts/chart_preview', function () { + loadTestFile(require.resolve('./alerts/chart_preview')); + }); + + describe('correlations/slow_transactions', function () { + loadTestFile(require.resolve('./correlations/slow_transactions')); + }); + + describe('metrics_charts/metrics_charts', function () { + loadTestFile(require.resolve('./metrics_charts/metrics_charts')); + }); + + describe('observability_overview/has_data', function () { + loadTestFile(require.resolve('./observability_overview/has_data')); + }); + + describe('observability_overview/observability_overview', function () { + loadTestFile(require.resolve('./observability_overview/observability_overview')); + }); + + describe('service_maps/service_maps', function () { + loadTestFile(require.resolve('./service_maps/service_maps')); + }); + + // Service overview + describe('service_overview/dependencies', function () { + loadTestFile(require.resolve('./service_overview/dependencies')); + }); - loadTestFile(require.resolve('./csm/csm_services')); - loadTestFile(require.resolve('./csm/has_rum_data')); - loadTestFile(require.resolve('./csm/js_errors')); - loadTestFile(require.resolve('./csm/long_task_metrics')); - loadTestFile(require.resolve('./csm/page_load_dist')); - loadTestFile(require.resolve('./csm/page_views')); - loadTestFile(require.resolve('./csm/url_search')); - loadTestFile(require.resolve('./csm/web_core_vitals')); + describe('service_overview/instances', function () { + loadTestFile(require.resolve('./service_overview/instances')); + }); - loadTestFile(require.resolve('./metrics_charts/metrics_charts')); + // Services + describe('services/agent_name', function () { + loadTestFile(require.resolve('./services/agent_name')); + }); - loadTestFile(require.resolve('./observability_overview/has_data')); - loadTestFile(require.resolve('./observability_overview/observability_overview')); - - loadTestFile(require.resolve('./service_maps/service_maps')); - - loadTestFile(require.resolve('./service_overview/dependencies')); - loadTestFile(require.resolve('./service_overview/instances')); - - loadTestFile(require.resolve('./services/agent_name')); - loadTestFile(require.resolve('./services/annotations')); - loadTestFile(require.resolve('./services/service_details')); - loadTestFile(require.resolve('./services/service_icons')); - loadTestFile(require.resolve('./services/throughput')); - loadTestFile(require.resolve('./services/top_services')); - loadTestFile(require.resolve('./services/transaction_types')); - loadTestFile(require.resolve('./services/error_groups_primary_statistics')); - loadTestFile(require.resolve('./services/error_groups_comparison_statistics')); - - loadTestFile(require.resolve('./settings/anomaly_detection/basic')); - loadTestFile(require.resolve('./settings/anomaly_detection/no_access_user')); - loadTestFile(require.resolve('./settings/anomaly_detection/read_user')); - loadTestFile(require.resolve('./settings/anomaly_detection/write_user')); - - loadTestFile(require.resolve('./settings/agent_configuration')); - - loadTestFile(require.resolve('./settings/custom_link')); - - loadTestFile(require.resolve('./traces/top_traces')); - - loadTestFile(require.resolve('./transactions/breakdown')); - loadTestFile(require.resolve('./transactions/distribution')); - loadTestFile(require.resolve('./transactions/error_rate')); - loadTestFile(require.resolve('./transactions/latency')); - loadTestFile(require.resolve('./transactions/throughput')); - loadTestFile(require.resolve('./transactions/top_transaction_groups')); - loadTestFile(require.resolve('./transactions/transactions_groups_primary_statistics')); - loadTestFile(require.resolve('./transactions/transactions_groups_comparison_statistics')); - - loadTestFile(require.resolve('./feature_controls')); + describe('services/annotations', function () { + loadTestFile(require.resolve('./services/annotations')); + }); + + describe('services/service_details', function () { + loadTestFile(require.resolve('./services/service_details')); + }); + + describe('services/service_icons', function () { + loadTestFile(require.resolve('./services/service_icons')); + }); + + describe('services/throughput', function () { + loadTestFile(require.resolve('./services/throughput')); + }); + + describe('services/top_services', function () { + loadTestFile(require.resolve('./services/top_services')); + }); + + describe('services/transaction_types', function () { + loadTestFile(require.resolve('./services/transaction_types')); + }); + + describe('services/error_groups_primary_statistics', function () { + loadTestFile(require.resolve('./services/error_groups_primary_statistics')); + }); + + describe('services/error_groups_comparison_statistics', function () { + loadTestFile(require.resolve('./services/error_groups_comparison_statistics')); + }); + + // Settinges + describe('settings/anomaly_detection/basic', function () { + loadTestFile(require.resolve('./settings/anomaly_detection/basic')); + }); + + describe('settings/anomaly_detection/no_access_user', function () { + loadTestFile(require.resolve('./settings/anomaly_detection/no_access_user')); + }); + + describe('settings/anomaly_detection/read_user', function () { + loadTestFile(require.resolve('./settings/anomaly_detection/read_user')); + }); + + describe('settings/anomaly_detection/write_user', function () { + loadTestFile(require.resolve('./settings/anomaly_detection/write_user')); + }); + + describe('settings/agent_configuration', function () { + loadTestFile(require.resolve('./settings/agent_configuration')); + }); + + describe('settings/custom_link', function () { + loadTestFile(require.resolve('./settings/custom_link')); + }); + + // traces + describe('traces/top_traces', function () { + loadTestFile(require.resolve('./traces/top_traces')); + }); + + // transactions + describe('transactions/breakdown', function () { + loadTestFile(require.resolve('./transactions/breakdown')); + }); + + describe('transactions/distribution', function () { + loadTestFile(require.resolve('./transactions/distribution')); + }); + + describe('transactions/error_rate', function () { + loadTestFile(require.resolve('./transactions/error_rate')); + }); + + describe('transactions/latency', function () { + loadTestFile(require.resolve('./transactions/latency')); + }); + + describe('transactions/throughput', function () { + loadTestFile(require.resolve('./transactions/throughput')); + }); + + describe('transactions/top_transaction_groups', function () { + loadTestFile(require.resolve('./transactions/top_transaction_groups')); + }); + + describe('transactions/transactions_groups_primary_statistics', function () { + loadTestFile(require.resolve('./transactions/transactions_groups_primary_statistics')); + }); + + describe('transactions/transactions_groups_comparison_statistics', function () { + loadTestFile(require.resolve('./transactions/transactions_groups_comparison_statistics')); + }); + + // feature control + describe('feature_controls', function () { + loadTestFile(require.resolve('./feature_controls')); + }); + + // CSM + describe('csm/csm_services', function () { + loadTestFile(require.resolve('./csm/csm_services')); + }); + + describe('csm/has_rum_data', function () { + loadTestFile(require.resolve('./csm/has_rum_data')); + }); + + describe('csm/js_errors', function () { + loadTestFile(require.resolve('./csm/js_errors')); + }); + + describe('csm/long_task_metrics', function () { + loadTestFile(require.resolve('./csm/long_task_metrics')); + }); + + describe('csm/page_load_dist', function () { + loadTestFile(require.resolve('./csm/page_load_dist')); + }); + + describe('csm/page_views', function () { + loadTestFile(require.resolve('./csm/page_views')); + }); + + describe('csm/url_search', function () { + loadTestFile(require.resolve('./csm/url_search')); + }); + + describe('csm/web_core_vitals', function () { + loadTestFile(require.resolve('./csm/web_core_vitals')); + }); registry.run(providerContext); }); diff --git a/x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts b/x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts index a86063bcab58a..c6bdce217e229 100644 --- a/x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts +++ b/x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts @@ -6,20 +6,24 @@ */ import expect from '@kbn/expect'; +import { createApmApiSupertest } from '../../common/apm_api_supertest'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); + const apmApiSupertest = createApmApiSupertest(supertest); registry.when( 'Observability overview when data is not loaded', { config: 'basic', archives: [] }, () => { it('returns false when there is no data', async () => { - const response = await supertest.get('/api/apm/observability_overview/has_data'); + const response = await apmApiSupertest({ + endpoint: 'GET /api/apm/observability_overview/has_data', + }); expect(response.status).to.be(200); - expect(response.body).to.eql(false); + expect(response.body.hasData).to.eql(false); }); } ); @@ -29,9 +33,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { { config: 'basic', archives: ['observability_overview'] }, () => { it('returns false when there is only onboarding data', async () => { - const response = await supertest.get('/api/apm/observability_overview/has_data'); + const response = await apmApiSupertest({ + endpoint: 'GET /api/apm/observability_overview/has_data', + }); expect(response.status).to.be(200); - expect(response.body).to.eql(false); + expect(response.body.hasData).to.eql(false); }); } ); @@ -41,9 +47,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { { config: 'basic', archives: ['apm_8.0.0'] }, () => { it('returns true when there is at least one document on transaction, error or metrics indices', async () => { - const response = await supertest.get('/api/apm/observability_overview/has_data'); + const response = await apmApiSupertest({ + endpoint: 'GET /api/apm/observability_overview/has_data', + }); expect(response.status).to.be(200); - expect(response.body).to.eql(true); + expect(response.body.hasData).to.eql(true); }); } ); diff --git a/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts b/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts index fde1210551816..396a51fe04bbc 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts @@ -7,8 +7,8 @@ import expect from '@kbn/expect'; import { last, omit, pick, sortBy } from 'lodash'; -import url from 'url'; import { ValuesType } from 'utility-types'; +import { createApmApiSupertest } from '../../../common/apm_api_supertest'; import { roundNumber } from '../../../utils'; import { ENVIRONMENT_ALL } from '../../../../../plugins/apm/common/environment_filter_values'; import { APIReturnType } from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; @@ -18,7 +18,7 @@ import { registry } from '../../../common/registry'; import { apmDependenciesMapping, createServiceDependencyDocs } from './es_utils'; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const apmApiSupertest = createApmApiSupertest(getService('supertest')); const es = getService('es'); const archiveName = 'apm_8.0.0'; @@ -29,20 +29,21 @@ export default function ApiTest({ getService }: FtrProviderContext) { { config: 'basic', archives: [] }, () => { it('handles the empty state', async () => { - const response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-java/dependencies`, + const response = await apmApiSupertest({ + endpoint: `GET /api/apm/services/{serviceName}/dependencies`, + params: { + path: { serviceName: 'opbeans-java' }, query: { start, end, numBuckets: 20, environment: ENVIRONMENT_ALL.value, }, - }) - ); + }, + }); expect(response.status).to.be(200); - expect(response.body).to.eql([]); + expect(response.body.dependencies).to.eql([]); }); } ); @@ -203,17 +204,18 @@ export default function ApiTest({ getService }: FtrProviderContext) { refresh: 'wait_for', }); - response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-java/dependencies`, + response = await apmApiSupertest({ + endpoint: `GET /api/apm/services/{serviceName}/dependencies`, + params: { + path: { serviceName: 'opbeans-java' }, query: { start, end, numBuckets: 20, environment: ENVIRONMENT_ALL.value, }, - }) - ); + }, + }); }); it('returns a 200', () => { @@ -221,11 +223,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns two dependencies', () => { - expect(response.body.length).to.be(2); + expect(response.body.dependencies.length).to.be(2); }); it('returns opbeans-node as a dependency', () => { - const opbeansNode = response.body.find( + const opbeansNode = response.body.dependencies.find( (item) => item.type === 'service' && item.serviceName === 'opbeans-node' ); @@ -261,7 +263,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns postgres as an external dependency', () => { - const postgres = response.body.find( + const postgres = response.body.dependencies.find( (item) => item.type === 'external' && item.name === 'postgres' ); @@ -302,17 +304,18 @@ export default function ApiTest({ getService }: FtrProviderContext) { }; before(async () => { - response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-python/dependencies`, + response = await apmApiSupertest({ + endpoint: `GET /api/apm/services/{serviceName}/dependencies`, + params: { + path: { serviceName: 'opbeans-python' }, query: { start, end, numBuckets: 20, environment: ENVIRONMENT_ALL.value, }, - }) - ); + }, + }); }); it('returns a successful response', () => { @@ -320,10 +323,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns at least one item', () => { - expect(response.body.length).to.be.greaterThan(0); + expect(response.body.dependencies.length).to.be.greaterThan(0); expectSnapshot( - omit(response.body[0], [ + omit(response.body.dependencies[0], [ 'errorRate.timeseries', 'throughput.timeseries', 'latency.timeseries', @@ -349,7 +352,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns the right names', () => { - const names = response.body.map((item) => item.name); + const names = response.body.dependencies.map((item) => item.name); expectSnapshot(names.sort()).toMatchInline(` Array [ "elasticsearch", @@ -361,7 +364,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns the right service names', () => { - const serviceNames = response.body + const serviceNames = response.body.dependencies .map((item) => (item.type === 'service' ? item.serviceName : undefined)) .filter(Boolean); @@ -374,7 +377,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns the right latency values', () => { const latencyValues = sortBy( - response.body.map((item) => ({ name: item.name, latency: item.latency.value })), + response.body.dependencies.map((item) => ({ + name: item.name, + latency: item.latency.value, + })), 'name' ); @@ -402,7 +408,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns the right throughput values', () => { const throughputValues = sortBy( - response.body.map((item) => ({ name: item.name, throughput: item.throughput.value })), + response.body.dependencies.map((item) => ({ + name: item.name, + throughput: item.throughput.value, + })), 'name' ); @@ -430,7 +439,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns the right impact values', () => { const impactValues = sortBy( - response.body.map((item) => ({ + response.body.dependencies.map((item) => ({ name: item.name, impact: item.impact, latency: item.latency.value, diff --git a/x-pack/test/apm_api_integration/tests/service_overview/instances.ts b/x-pack/test/apm_api_integration/tests/service_overview/instances.ts index 8ff493b5575b0..776087b56b21d 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/instances.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/instances.ts @@ -6,34 +6,30 @@ */ import expect from '@kbn/expect'; -import url from 'url'; import { pick, sortBy } from 'lodash'; -import { isFiniteNumber } from '../../../../plugins/apm/common/utils/is_finite_number'; import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { isFiniteNumber } from '../../../../plugins/apm/common/utils/is_finite_number'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import archives from '../../common/fixtures/es_archiver/archives_metadata'; import { registry } from '../../common/registry'; +import { createApmApiSupertest } from '../../common/apm_api_supertest'; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const apmApiSupertest = createApmApiSupertest(getService('supertest')); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; - interface Response { - status: number; - body: APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances'>; - } - registry.when( 'Service overview instances when data is not loaded', { config: 'basic', archives: [] }, () => { describe('when data is not loaded', () => { it('handles the empty state', async () => { - const response: Response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-java/service_overview_instances`, + const response = await apmApiSupertest({ + endpoint: `GET /api/apm/services/{serviceName}/service_overview_instances`, + params: { + path: { serviceName: 'opbeans-java' }, query: { latencyAggregationType: 'avg', start, @@ -41,11 +37,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { numBuckets: 20, transactionType: 'request', }, - }) - ); + }, + }); expect(response.status).to.be(200); - expect(response.body).to.eql([]); + expect(response.body.serviceInstances).to.eql([]); }); }); } @@ -56,12 +52,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { { config: 'basic', archives: [archiveName] }, () => { describe('fetching java data', () => { - let response: Response; + let response: { + body: APIReturnType<`GET /api/apm/services/{serviceName}/service_overview_instances`>; + }; beforeEach(async () => { - response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-java/service_overview_instances`, + response = await apmApiSupertest({ + endpoint: `GET /api/apm/services/{serviceName}/service_overview_instances`, + params: { + path: { serviceName: 'opbeans-java' }, query: { latencyAggregationType: 'avg', start, @@ -69,16 +68,16 @@ export default function ApiTest({ getService }: FtrProviderContext) { numBuckets: 20, transactionType: 'request', }, - }) - ); + }, + }); }); it('returns a service node item', () => { - expect(response.body.length).to.be.greaterThan(0); + expect(response.body.serviceInstances.length).to.be.greaterThan(0); }); it('returns statistics for each service node', () => { - const item = response.body[0]; + const item = response.body.serviceInstances[0]; expect(isFiniteNumber(item.cpuUsage?.value)).to.be(true); expect(isFiniteNumber(item.memoryUsage?.value)).to.be(true); @@ -94,7 +93,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns the right data', () => { - const items = sortBy(response.body, 'serviceNodeName'); + const items = sortBy(response.body.serviceInstances, 'serviceNodeName'); const serviceNodeNames = items.map((item) => item.serviceNodeName); @@ -139,12 +138,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); describe('fetching non-java data', () => { - let response: Response; + let response: { + body: APIReturnType<`GET /api/apm/services/{serviceName}/service_overview_instances`>; + }; beforeEach(async () => { - response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-ruby/service_overview_instances`, + response = await apmApiSupertest({ + endpoint: `GET /api/apm/services/{serviceName}/service_overview_instances`, + params: { + path: { serviceName: 'opbeans-ruby' }, query: { latencyAggregationType: 'avg', start, @@ -152,12 +154,12 @@ export default function ApiTest({ getService }: FtrProviderContext) { numBuckets: 20, transactionType: 'request', }, - }) - ); + }, + }); }); it('returns statistics for each service node', () => { - const item = response.body[0]; + const item = response.body.serviceInstances[0]; expect(isFiniteNumber(item.cpuUsage?.value)).to.be(true); expect(isFiniteNumber(item.memoryUsage?.value)).to.be(true); @@ -173,7 +175,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns the right data', () => { - const items = sortBy(response.body, 'serviceNodeName'); + const items = sortBy(response.body.serviceInstances, 'serviceNodeName'); const serviceNodeNames = items.map((item) => item.serviceNodeName); diff --git a/x-pack/test/apm_api_integration/tests/services/service_icons.ts b/x-pack/test/apm_api_integration/tests/services/service_icons.ts index 2c7313e4d01ef..94188b6321775 100644 --- a/x-pack/test/apm_api_integration/tests/services/service_icons.ts +++ b/x-pack/test/apm_api_integration/tests/services/service_icons.ts @@ -46,11 +46,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); expectSnapshot(response.body).toMatchInline(` - Object { - "agentName": "java", - "containerType": "Kubernetes", - } - `); + Object { + "agentName": "java", + "containerType": "Kubernetes", + } + `); }); it('returns python service icons', async () => { @@ -64,12 +64,12 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); expectSnapshot(response.body).toMatchInline(` - Object { - "agentName": "python", - "cloudProvider": "gcp", - "containerType": "Kubernetes", - } - `); + Object { + "agentName": "python", + "cloudProvider": "gcp", + "containerType": "Kubernetes", + } + `); }); } ); diff --git a/x-pack/test/apm_api_integration/tests/settings/agent_configuration.ts b/x-pack/test/apm_api_integration/tests/settings/agent_configuration.ts index 14a5cf66c4090..fbd60c0f1ab1a 100644 --- a/x-pack/test/apm_api_integration/tests/settings/agent_configuration.ts +++ b/x-pack/test/apm_api_integration/tests/settings/agent_configuration.ts @@ -9,104 +9,77 @@ import expect from '@kbn/expect'; import { omit, orderBy } from 'lodash'; import { AgentConfigurationIntake } from '../../../../plugins/apm/common/agent_configuration/configuration_types'; import { AgentConfigSearchParams } from '../../../../plugins/apm/server/routes/settings/agent_configuration'; +import { createApmApiSupertest } from '../../common/apm_api_supertest'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function agentConfigurationTests({ getService }: FtrProviderContext) { - const supertestRead = getService('supertestAsApmReadUser'); - const supertestWrite = getService('supertestAsApmWriteUser'); + const supertestRead = createApmApiSupertest(getService('supertestAsApmReadUser')); + const supertestWrite = createApmApiSupertest(getService('supertestAsApmWriteUser')); + const log = getService('log'); const archiveName = 'apm_8.0.0'; function getServices() { - return supertestRead - .get(`/api/apm/settings/agent-configuration/services`) - .set('kbn-xsrf', 'foo'); + return supertestRead({ + endpoint: 'GET /api/apm/settings/agent-configuration/services', + }); } - function getEnvironments(serviceName: string) { - return supertestRead - .get(`/api/apm/settings/agent-configuration/environments?serviceName=${serviceName}`) - .set('kbn-xsrf', 'foo'); + async function getEnvironments(serviceName: string) { + return supertestRead({ + endpoint: 'GET /api/apm/settings/agent-configuration/environments', + params: { query: { serviceName } }, + }); } function getAgentName(serviceName: string) { - return supertestRead - .get(`/api/apm/settings/agent-configuration/agent_name?serviceName=${serviceName}`) - .set('kbn-xsrf', 'foo'); + return supertestRead({ + endpoint: 'GET /api/apm/settings/agent-configuration/agent_name', + params: { query: { serviceName } }, + }); } function searchConfigurations(configuration: AgentConfigSearchParams) { - return supertestRead - .post(`/api/apm/settings/agent-configuration/search`) - .send(configuration) - .set('kbn-xsrf', 'foo'); + return supertestRead({ + endpoint: 'POST /api/apm/settings/agent-configuration/search', + params: { body: configuration }, + }); } function getAllConfigurations() { - return supertestRead.get(`/api/apm/settings/agent-configuration`).set('kbn-xsrf', 'foo'); + return supertestRead({ endpoint: 'GET /api/apm/settings/agent-configuration' }); } - async function createConfiguration(config: AgentConfigurationIntake, { user = 'write' } = {}) { - log.debug('creating configuration', config.service); + function createConfiguration(configuration: AgentConfigurationIntake, { user = 'write' } = {}) { + log.debug('creating configuration', configuration.service); const supertestClient = user === 'read' ? supertestRead : supertestWrite; - const res = await supertestClient - .put(`/api/apm/settings/agent-configuration`) - .send(config) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; + return supertestClient({ + endpoint: 'PUT /api/apm/settings/agent-configuration', + params: { body: configuration }, + }); } - async function updateConfiguration(config: AgentConfigurationIntake, { user = 'write' } = {}) { + function updateConfiguration(config: AgentConfigurationIntake, { user = 'write' } = {}) { log.debug('updating configuration', config.service); const supertestClient = user === 'read' ? supertestRead : supertestWrite; - const res = await supertestClient - .put(`/api/apm/settings/agent-configuration?overwrite=true`) - .send(config) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; + return supertestClient({ + endpoint: 'PUT /api/apm/settings/agent-configuration', + params: { query: { overwrite: true }, body: config }, + }); } - async function deleteConfiguration( - { service }: AgentConfigurationIntake, - { user = 'write' } = {} - ) { + function deleteConfiguration({ service }: AgentConfigurationIntake, { user = 'write' } = {}) { log.debug('deleting configuration', service); const supertestClient = user === 'read' ? supertestRead : supertestWrite; - const res = await supertestClient - .delete(`/api/apm/settings/agent-configuration`) - .send({ service }) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; - } - - function throwOnError(res: any) { - const { statusCode, req, body } = res; - if (statusCode !== 200) { - const e = new Error(` - Endpoint: ${req.method} ${req.path} - Service: ${JSON.stringify(res.request._data.service)} - Status code: ${statusCode} - Response: ${body.message}`); - - // @ts-ignore - e.res = res; - - throw e; - } + return supertestClient({ + endpoint: 'DELETE /api/apm/settings/agent-configuration', + params: { body: { service } }, + }); } registry.when( @@ -115,17 +88,17 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte () => { it('handles the empty state for services', async () => { const { body } = await getServices(); - expect(body).to.eql(['ALL_OPTION_VALUE']); + expect(body.serviceNames).to.eql(['ALL_OPTION_VALUE']); }); it('handles the empty state for environments', async () => { const { body } = await getEnvironments('myservice'); - expect(body).to.eql([{ name: 'ALL_OPTION_VALUE', alreadyConfigured: false }]); + expect(body.environments).to.eql([{ name: 'ALL_OPTION_VALUE', alreadyConfigured: false }]); }); - it('handles the empty state for agent names', async () => { + it('handles the empty state for agent name', async () => { const { body } = await getAgentName('myservice'); - expect(body).to.eql({}); + expect(body.agentName).to.eql(undefined); }); describe('as a read-only user', () => { @@ -160,7 +133,7 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte try { await deleteConfiguration(newConfig, { user: 'read' }); - // ensure that `deleteConfiguration` throws + // ensure that line above throws expect(true).to.be(false); } catch (e) { expect(e.res.statusCode).to.be(403); @@ -182,18 +155,19 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte it('can create and delete config', async () => { // assert that config does not exist - const res1 = await searchConfigurations(searchParams); - expect(res1.status).to.equal(404); + await expectStatusCode(() => searchConfigurations(searchParams), 404); - // assert that config was created + // create config await createConfiguration(newConfig); - const res2 = await searchConfigurations(searchParams); - expect(res2.status).to.equal(200); - // assert that config was deleted + // assert that config now exists + await expectStatusCode(() => searchConfigurations(searchParams), 200); + + // delete config await deleteConfiguration(newConfig); - const res3 = await searchConfigurations(searchParams); - expect(res3.status).to.equal(404); + + // assert that config was deleted + await expectStatusCode(() => searchConfigurations(searchParams), 404); }); describe('when a configuration exists', () => { @@ -209,8 +183,9 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte it('can list the config', async () => { const { status, body } = await getAllConfigurations(); + expect(status).to.equal(200); - expect(omitTimestamp(body)).to.eql([ + expect(omitTimestamp(body.configurations)).to.eql([ { service: {}, settings: { transaction_sample_rate: '0.55' }, @@ -295,7 +270,9 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte it('can list all configs', async () => { const { status, body } = await getAllConfigurations(); expect(status).to.equal(200); - expect(orderBy(omitTimestamp(body), ['settings.transaction_sample_rate'])).to.eql([ + expect( + orderBy(omitTimestamp(body.configurations), ['settings.transaction_sample_rate']) + ).to.eql([ { service: {}, settings: { transaction_sample_rate: '0.1' }, @@ -351,7 +328,7 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte service: { name: 'myservice', environment: 'production' }, settings: { transaction_sample_rate: '0.9' }, }; - let etag: string; + let etag: string | undefined; before(async () => { log.debug('creating agent configuration'); @@ -391,7 +368,7 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte service: { name: 'myservice', environment: 'development' }, }); - return body._source.applied_by_agent; + return !!body._source.applied_by_agent; } // wait until `applied_by_agent` has been updated in elasticsearch @@ -415,7 +392,7 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte service: { name: 'myservice', environment: 'production' }, }); - return body._source.applied_by_agent; + return !!body._source.applied_by_agent; } // wait until `applied_by_agent` has been updated in elasticsearch @@ -432,47 +409,56 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte it('returns all services', async () => { const { body } = await getServices(); expectSnapshot(body).toMatchInline(` - Array [ - "ALL_OPTION_VALUE", - "kibana", - "kibana-frontend", - "opbeans-dotnet", - "opbeans-go", - "opbeans-java", - "opbeans-node", - "opbeans-python", - "opbeans-ruby", - "opbeans-rum", - ] + Object { + "serviceNames": Array [ + "ALL_OPTION_VALUE", + "kibana", + "kibana-frontend", + "opbeans-dotnet", + "opbeans-go", + "opbeans-java", + "opbeans-node", + "opbeans-python", + "opbeans-ruby", + "opbeans-rum", + ], + } `); }); it('returns the environments, all unconfigured', async () => { const { body } = await getEnvironments('opbeans-node'); + const { environments } = body; - expect(body.map((item: { name: string }) => item.name)).to.contain('ALL_OPTION_VALUE'); + expect(environments.map((item: { name: string }) => item.name)).to.contain( + 'ALL_OPTION_VALUE' + ); expect( - body.every((item: { alreadyConfigured: boolean }) => item.alreadyConfigured === false) + environments.every( + (item: { alreadyConfigured: boolean }) => item.alreadyConfigured === false + ) ).to.be(true); expectSnapshot(body).toMatchInline(` - Array [ - Object { - "alreadyConfigured": false, - "name": "ALL_OPTION_VALUE", - }, - Object { - "alreadyConfigured": false, - "name": "testing", - }, - ] + Object { + "environments": Array [ + Object { + "alreadyConfigured": false, + "name": "ALL_OPTION_VALUE", + }, + Object { + "alreadyConfigured": false, + "name": "testing", + }, + ], + } `); }); - it('returns the agent names', async () => { + it('returns the agent name', async () => { const { body } = await getAgentName('opbeans-node'); - expect(body).to.eql({ agentName: 'nodejs' }); + expect(body.agentName).to.eql('nodejs'); }); } ); @@ -494,3 +480,17 @@ async function waitFor(cb: () => Promise, retries = 50): Promise omit(config, '@timestamp')); } + +async function expectStatusCode( + fn: () => Promise<{ + status: number; + }>, + statusCode: number +) { + try { + const res = await fn(); + expect(res.status).to.be(statusCode); + } catch (e) { + expect(e.res.status).to.be(statusCode); + } +} diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts index 83ff51ec1b4c2..322c2a4a049cf 100644 --- a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts @@ -7,21 +7,25 @@ import expect from '@kbn/expect'; import { countBy } from 'lodash'; +import { createApmApiSupertest } from '../../../common/apm_api_supertest'; import { registry } from '../../../common/registry'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function apiTest({ getService }: FtrProviderContext) { const apmWriteUser = getService('supertestAsApmWriteUser'); + const apmApiWriteUser = createApmApiSupertest(getService('supertestAsApmWriteUser')); function getJobs() { - return apmWriteUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); + return apmApiWriteUser({ endpoint: `GET /api/apm/settings/anomaly-detection/jobs` }); } function createJobs(environments: string[]) { - return apmWriteUser - .post(`/api/apm/settings/anomaly-detection/jobs`) - .send({ environments }) - .set('kbn-xsrf', 'foo'); + return apmApiWriteUser({ + endpoint: `POST /api/apm/settings/anomaly-detection/jobs`, + params: { + body: { environments }, + }, + }); } function deleteJobs(jobIds: string[]) { diff --git a/x-pack/test/apm_api_integration/tests/settings/custom_link.ts b/x-pack/test/apm_api_integration/tests/settings/custom_link.ts index 49b18be3580cf..c26594745112c 100644 --- a/x-pack/test/apm_api_integration/tests/settings/custom_link.ts +++ b/x-pack/test/apm_api_integration/tests/settings/custom_link.ts @@ -5,15 +5,15 @@ * 2.0. */ -import URL from 'url'; import expect from '@kbn/expect'; import { CustomLink } from '../../../../plugins/apm/common/custom_link/custom_link_types'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; +import { createApmApiSupertest } from '../../common/apm_api_supertest'; export default function customLinksTests({ getService }: FtrProviderContext) { - const supertestRead = getService('supertest'); - const supertestWrite = getService('supertestAsApmWriteUser'); + const supertestRead = createApmApiSupertest(getService('supertest')); + const supertestWrite = createApmApiSupertest(getService('supertestAsApmWriteUser')); const log = getService('log'); const archiveName = 'apm_8.0.0'; @@ -28,16 +28,16 @@ export default function customLinksTests({ getService }: FtrProviderContext) { { key: 'transaction.type', value: 'qux' }, ], } as CustomLink; - const response = await supertestWrite - .post(`/api/apm/settings/custom_links`) - .send(customLink) - .set('kbn-xsrf', 'foo'); - expect(response.status).to.be(403); - - expectSnapshot(response.body.message).toMatchInline( - `"To create custom links, you must be subscribed to an Elastic Gold license or above. With it, you'll have the ability to create custom links to improve your workflow when analyzing your services."` - ); + try { + await createCustomLink(customLink); + expect(true).to.be(false); + } catch (e) { + expect(e.res.status).to.be(403); + expectSnapshot(e.res.body.message).toMatchInline( + `"To create custom links, you must be subscribed to an Elastic Gold license or above. With it, you'll have the ability to create custom links to improve your workflow when analyzing your services."` + ); + } }); }); @@ -56,12 +56,13 @@ export default function customLinksTests({ getService }: FtrProviderContext) { } as CustomLink; await createCustomLink(customLink); }); + it('fetches a custom link', async () => { const { status, body } = await searchCustomLinks({ 'service.name': 'baz', 'transaction.type': 'qux', }); - const { label, url, filters } = body[0]; + const { label, url, filters } = body.customLinks[0]; expect(status).to.equal(200); expect({ label, url, filters }).to.eql({ @@ -73,13 +74,16 @@ export default function customLinksTests({ getService }: FtrProviderContext) { ], }); }); + it('updates a custom link', async () => { - let { status, body } = await searchCustomLinks({ + const { status, body } = await searchCustomLinks({ 'service.name': 'baz', 'transaction.type': 'qux', }); expect(status).to.equal(200); - await updateCustomLink(body[0].id, { + + const id = body.customLinks[0].id!; + await updateCustomLink(id, { label: 'foo', url: 'https://elastic.co?service.name={{service.name}}', filters: [ @@ -87,12 +91,14 @@ export default function customLinksTests({ getService }: FtrProviderContext) { { key: 'transaction.name', value: 'bar' }, ], }); - ({ status, body } = await searchCustomLinks({ + + const { status: newStatus, body: newBody } = await searchCustomLinks({ 'service.name': 'quz', 'transaction.name': 'bar', - })); - const { label, url, filters } = body[0]; - expect(status).to.equal(200); + }); + + const { label, url, filters } = newBody.customLinks[0]; + expect(newStatus).to.equal(200); expect({ label, url, filters }).to.eql({ label: 'foo', url: 'https://elastic.co?service.name={{service.name}}', @@ -102,26 +108,36 @@ export default function customLinksTests({ getService }: FtrProviderContext) { ], }); }); + it('deletes a custom link', async () => { - let { status, body } = await searchCustomLinks({ + const { status, body } = await searchCustomLinks({ 'service.name': 'quz', 'transaction.name': 'bar', }); expect(status).to.equal(200); - await deleteCustomLink(body[0].id); - ({ status, body } = await searchCustomLinks({ + expect(body.customLinks.length).to.be(1); + + const id = body.customLinks[0].id!; + await deleteCustomLink(id); + + const { status: newStatus, body: newBody } = await searchCustomLinks({ 'service.name': 'quz', 'transaction.name': 'bar', - })); - expect(status).to.equal(200); - expect(body).to.eql([]); + }); + expect(newStatus).to.equal(200); + expect(newBody.customLinks.length).to.be(0); }); describe('transaction', () => { it('fetches a transaction sample', async () => { - const response = await supertestRead.get( - '/api/apm/settings/custom_links/transaction?service.name=opbeans-java' - ); + const response = await supertestRead({ + endpoint: 'GET /api/apm/settings/custom_links/transaction', + params: { + query: { + 'service.name': 'opbeans-java', + }, + }, + }); expect(response.status).to.be(200); expect(response.body.service.name).to.eql('opbeans-java'); }); @@ -130,56 +146,43 @@ export default function customLinksTests({ getService }: FtrProviderContext) { ); function searchCustomLinks(filters?: any) { - const path = URL.format({ - pathname: `/api/apm/settings/custom_links`, - query: filters, + return supertestRead({ + endpoint: 'GET /api/apm/settings/custom_links', + params: { + query: filters, + }, }); - return supertestRead.get(path).set('kbn-xsrf', 'foo'); } async function createCustomLink(customLink: CustomLink) { log.debug('creating configuration', customLink); - const res = await supertestWrite - .post(`/api/apm/settings/custom_links`) - .send(customLink) - .set('kbn-xsrf', 'foo'); - throwOnError(res); - - return res; + return supertestWrite({ + endpoint: 'POST /api/apm/settings/custom_links', + params: { + body: customLink, + }, + }); } async function updateCustomLink(id: string, customLink: CustomLink) { log.debug('updating configuration', id, customLink); - const res = await supertestWrite - .put(`/api/apm/settings/custom_links/${id}`) - .send(customLink) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - return res; + return supertestWrite({ + endpoint: 'PUT /api/apm/settings/custom_links/{id}', + params: { + path: { id }, + body: customLink, + }, + }); } async function deleteCustomLink(id: string) { log.debug('deleting configuration', id); - const res = await supertestWrite - .delete(`/api/apm/settings/custom_links/${id}`) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; - } - function throwOnError(res: any) { - const { statusCode, req, body } = res; - if (statusCode !== 200) { - throw new Error(` - Endpoint: ${req.method} ${req.path} - Service: ${JSON.stringify(res.request._data.service)} - Status code: ${statusCode} - Response: ${body.message}`); - } + return supertestWrite({ + endpoint: 'DELETE /api/apm/settings/custom_links/{id}', + params: { path: { id } }, + }); } }