mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 03:01:21 -04:00
[APM] Extract server type utils to package (#96349)
This commit is contained in:
parent
d9ef5c28d5
commit
bfc940c146
76 changed files with 2821 additions and 1684 deletions
|
@ -131,10 +131,12 @@
|
||||||
"@kbn/crypto": "link:packages/kbn-crypto",
|
"@kbn/crypto": "link:packages/kbn-crypto",
|
||||||
"@kbn/i18n": "link:packages/kbn-i18n",
|
"@kbn/i18n": "link:packages/kbn-i18n",
|
||||||
"@kbn/interpreter": "link:packages/kbn-interpreter",
|
"@kbn/interpreter": "link:packages/kbn-interpreter",
|
||||||
|
"@kbn/io-ts-utils": "link:packages/kbn-io-ts-utils",
|
||||||
"@kbn/legacy-logging": "link:packages/kbn-legacy-logging",
|
"@kbn/legacy-logging": "link:packages/kbn-legacy-logging",
|
||||||
"@kbn/logging": "link:packages/kbn-logging",
|
"@kbn/logging": "link:packages/kbn-logging",
|
||||||
"@kbn/monaco": "link:packages/kbn-monaco",
|
"@kbn/monaco": "link:packages/kbn-monaco",
|
||||||
"@kbn/server-http-tools": "link:packages/kbn-server-http-tools",
|
"@kbn/server-http-tools": "link:packages/kbn-server-http-tools",
|
||||||
|
"@kbn/server-route-repository": "link:packages/kbn-server-route-repository",
|
||||||
"@kbn/std": "link:packages/kbn-std",
|
"@kbn/std": "link:packages/kbn-std",
|
||||||
"@kbn/tinymath": "link:packages/kbn-tinymath",
|
"@kbn/tinymath": "link:packages/kbn-tinymath",
|
||||||
"@kbn/ui-framework": "link:packages/kbn-ui-framework",
|
"@kbn/ui-framework": "link:packages/kbn-ui-framework",
|
||||||
|
|
13
packages/kbn-io-ts-utils/jest.config.js
Normal file
13
packages/kbn-io-ts-utils/jest.config.js
Normal file
|
@ -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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
preset: '@kbn/test',
|
||||||
|
rootDir: '../..',
|
||||||
|
roots: ['<rootDir>/packages/kbn-io-ts-utils'],
|
||||||
|
};
|
13
packages/kbn-io-ts-utils/package.json
Normal file
13
packages/kbn-io-ts-utils/package.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"name": "@kbn/io-ts-utils",
|
||||||
|
"main": "./target/index.js",
|
||||||
|
"types": "./target/index.d.ts",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "SSPL-1.0 OR Elastic License 2.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "../../node_modules/.bin/tsc",
|
||||||
|
"kbn:bootstrap": "yarn build",
|
||||||
|
"kbn:watch": "yarn build --watch"
|
||||||
|
}
|
||||||
|
}
|
11
packages/kbn-io-ts-utils/src/index.ts
Normal file
11
packages/kbn-io-ts-utils/src/index.ts
Normal file
|
@ -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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { jsonRt } from './json_rt';
|
||||||
|
export { mergeRt } from './merge_rt';
|
||||||
|
export { strictKeysRt } from './strict_keys_rt';
|
|
@ -1,8 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
* 2.0.
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as t from 'io-ts';
|
import * as t from 'io-ts';
|
||||||
|
@ -12,9 +13,7 @@ import { Right } from 'fp-ts/lib/Either';
|
||||||
import { pipe } from 'fp-ts/lib/pipeable';
|
import { pipe } from 'fp-ts/lib/pipeable';
|
||||||
import { identity } from 'fp-ts/lib/function';
|
import { identity } from 'fp-ts/lib/function';
|
||||||
|
|
||||||
function getValueOrThrow<TEither extends Either<any, any>>(
|
function getValueOrThrow<TEither extends Either<any, any>>(either: TEither): Right<TEither> {
|
||||||
either: TEither
|
|
||||||
): Right<TEither> {
|
|
||||||
const value = pipe(
|
const value = pipe(
|
||||||
either,
|
either,
|
||||||
fold(() => {
|
fold(() => {
|
|
@ -1,8 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
* 2.0.
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as t from 'io-ts';
|
import * as t from 'io-ts';
|
|
@ -1,18 +1,19 @@
|
||||||
/*
|
/*
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
* 2.0.
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as t from 'io-ts';
|
import * as t from 'io-ts';
|
||||||
import { isLeft } from 'fp-ts/lib/Either';
|
import { isLeft } from 'fp-ts/lib/Either';
|
||||||
import { merge } from './';
|
import { mergeRt } from '.';
|
||||||
import { jsonRt } from '../json_rt';
|
import { jsonRt } from '../json_rt';
|
||||||
|
|
||||||
describe('merge', () => {
|
describe('merge', () => {
|
||||||
it('fails on one or more errors', () => {
|
it('fails on one or more errors', () => {
|
||||||
const type = merge([t.type({ foo: t.string }), t.type({ bar: t.number })]);
|
const type = mergeRt(t.type({ foo: t.string }), t.type({ bar: t.number }));
|
||||||
|
|
||||||
const result = type.decode({ foo: '' });
|
const result = type.decode({ foo: '' });
|
||||||
|
|
||||||
|
@ -20,10 +21,7 @@ describe('merge', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('merges left to right', () => {
|
it('merges left to right', () => {
|
||||||
const typeBoolean = merge([
|
const typeBoolean = mergeRt(t.type({ foo: t.string }), t.type({ foo: jsonRt.pipe(t.boolean) }));
|
||||||
t.type({ foo: t.string }),
|
|
||||||
t.type({ foo: jsonRt.pipe(t.boolean) }),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const resultBoolean = typeBoolean.decode({
|
const resultBoolean = typeBoolean.decode({
|
||||||
foo: 'true',
|
foo: 'true',
|
||||||
|
@ -34,10 +32,7 @@ describe('merge', () => {
|
||||||
foo: true,
|
foo: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const typeString = merge([
|
const typeString = mergeRt(t.type({ foo: jsonRt.pipe(t.boolean) }), t.type({ foo: t.string }));
|
||||||
t.type({ foo: jsonRt.pipe(t.boolean) }),
|
|
||||||
t.type({ foo: t.string }),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const resultString = typeString.decode({
|
const resultString = typeString.decode({
|
||||||
foo: 'true',
|
foo: 'true',
|
||||||
|
@ -50,10 +45,10 @@ describe('merge', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('deeply merges values', () => {
|
it('deeply merges values', () => {
|
||||||
const type = merge([
|
const type = mergeRt(
|
||||||
t.type({ foo: t.type({ baz: t.string }) }),
|
t.type({ foo: t.type({ baz: t.string }) }),
|
||||||
t.type({ foo: t.type({ bar: t.string }) }),
|
t.type({ foo: t.type({ bar: t.string }) })
|
||||||
]);
|
);
|
||||||
|
|
||||||
const result = type.decode({
|
const result = type.decode({
|
||||||
foo: {
|
foo: {
|
|
@ -1,31 +1,40 @@
|
||||||
/*
|
/*
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
* 2.0.
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as t from 'io-ts';
|
import * as t from 'io-ts';
|
||||||
import { merge as lodashMerge } from 'lodash';
|
import { merge as lodashMerge } from 'lodash';
|
||||||
import { isLeft } from 'fp-ts/lib/Either';
|
import { isLeft } from 'fp-ts/lib/Either';
|
||||||
import { ValuesType } from 'utility-types';
|
|
||||||
|
|
||||||
export type MergeType<
|
type PlainObject = Record<string | number | symbol, any>;
|
||||||
T extends t.Any[],
|
|
||||||
U extends ValuesType<T> = ValuesType<T>
|
type DeepMerge<T, U> = U extends PlainObject
|
||||||
> = t.Type<U['_A'], U['_O'], U['_I']> & {
|
? T extends PlainObject
|
||||||
_tag: 'MergeType';
|
? Omit<T, keyof U> &
|
||||||
types: T;
|
{
|
||||||
};
|
[key in keyof U]: T extends { [k in key]: any } ? DeepMerge<T[key], U[key]> : U[key];
|
||||||
|
}
|
||||||
|
: U
|
||||||
|
: U;
|
||||||
|
|
||||||
// this is similar to t.intersection, but does a deep merge
|
// this is similar to t.intersection, but does a deep merge
|
||||||
// instead of a shallow merge
|
// instead of a shallow merge
|
||||||
|
|
||||||
export function merge<A extends t.Mixed, B extends t.Mixed>(
|
export type MergeType<T1 extends t.Any, T2 extends t.Any> = t.Type<
|
||||||
types: [A, B]
|
DeepMerge<t.TypeOf<T1>, t.TypeOf<T2>>,
|
||||||
): MergeType<[A, B]>;
|
DeepMerge<t.OutputOf<T1>, t.OutputOf<T2>>
|
||||||
|
> & {
|
||||||
|
_tag: 'MergeType';
|
||||||
|
types: [T1, T2];
|
||||||
|
};
|
||||||
|
|
||||||
export function merge(types: t.Any[]) {
|
export function mergeRt<T1 extends t.Any, T2 extends t.Any>(a: T1, b: T2): MergeType<T1, T2>;
|
||||||
|
|
||||||
|
export function mergeRt(...types: t.Any[]) {
|
||||||
const mergeType = new t.Type(
|
const mergeType = new t.Type(
|
||||||
'merge',
|
'merge',
|
||||||
(u): u is unknown => {
|
(u): u is unknown => {
|
|
@ -1,8 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
* 2.0.
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as t from 'io-ts';
|
import * as t from 'io-ts';
|
||||||
|
@ -14,10 +15,7 @@ describe('strictKeysRt', () => {
|
||||||
it('correctly and deeply validates object keys', () => {
|
it('correctly and deeply validates object keys', () => {
|
||||||
const checks: Array<{ type: t.Type<any>; passes: any[]; fails: any[] }> = [
|
const checks: Array<{ type: t.Type<any>; passes: any[]; fails: any[] }> = [
|
||||||
{
|
{
|
||||||
type: t.intersection([
|
type: t.intersection([t.type({ foo: t.string }), t.partial({ bar: t.string })]),
|
||||||
t.type({ foo: t.string }),
|
|
||||||
t.partial({ bar: t.string }),
|
|
||||||
]),
|
|
||||||
passes: [{ foo: '' }, { foo: '', bar: '' }],
|
passes: [{ foo: '' }, { foo: '', bar: '' }],
|
||||||
fails: [
|
fails: [
|
||||||
{ foo: '', unknownKey: '' },
|
{ foo: '', unknownKey: '' },
|
||||||
|
@ -26,15 +24,9 @@ describe('strictKeysRt', () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: t.type({
|
type: t.type({
|
||||||
path: t.union([
|
path: t.union([t.type({ serviceName: t.string }), t.type({ transactionType: t.string })]),
|
||||||
t.type({ serviceName: t.string }),
|
|
||||||
t.type({ transactionType: t.string }),
|
|
||||||
]),
|
|
||||||
}),
|
}),
|
||||||
passes: [
|
passes: [{ path: { serviceName: '' } }, { path: { transactionType: '' } }],
|
||||||
{ path: { serviceName: '' } },
|
|
||||||
{ path: { transactionType: '' } },
|
|
||||||
],
|
|
||||||
fails: [
|
fails: [
|
||||||
{ path: { serviceName: '', unknownKey: '' } },
|
{ path: { serviceName: '', unknownKey: '' } },
|
||||||
{ path: { transactionType: '', unknownKey: '' } },
|
{ path: { transactionType: '', unknownKey: '' } },
|
||||||
|
@ -62,9 +54,7 @@ describe('strictKeysRt', () => {
|
||||||
|
|
||||||
if (!isRight(result)) {
|
if (!isRight(result)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Expected ${JSON.stringify(
|
`Expected ${JSON.stringify(value)} to be allowed, but validation failed with ${
|
||||||
value
|
|
||||||
)} to be allowed, but validation failed with ${
|
|
||||||
result.left[0].message
|
result.left[0].message
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
|
@ -76,9 +66,7 @@ describe('strictKeysRt', () => {
|
||||||
|
|
||||||
if (!isLeft(result)) {
|
if (!isLeft(result)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Expected ${JSON.stringify(
|
`Expected ${JSON.stringify(value)} to be disallowed, but validation succeeded`
|
||||||
value
|
|
||||||
)} to be disallowed, but validation succeeded`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -1,14 +1,15 @@
|
||||||
/*
|
/*
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
* 2.0.
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as t from 'io-ts';
|
import * as t from 'io-ts';
|
||||||
import { either, isRight } from 'fp-ts/lib/Either';
|
import { either, isRight } from 'fp-ts/lib/Either';
|
||||||
import { mapValues, difference, isPlainObject, forEach } from 'lodash';
|
import { mapValues, difference, isPlainObject, forEach } from 'lodash';
|
||||||
import { MergeType, merge } from '../merge';
|
import { MergeType, mergeRt } from '../merge_rt';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Type that tracks validated keys, and fails when the input value
|
Type that tracks validated keys, and fails when the input value
|
||||||
|
@ -21,7 +22,7 @@ type ParsableType =
|
||||||
| t.PartialType<any>
|
| t.PartialType<any>
|
||||||
| t.ExactType<any>
|
| t.ExactType<any>
|
||||||
| t.InterfaceType<any>
|
| t.InterfaceType<any>
|
||||||
| MergeType<any>;
|
| MergeType<any, any>;
|
||||||
|
|
||||||
function getKeysInObject<T extends Record<string, unknown>>(
|
function getKeysInObject<T extends Record<string, unknown>>(
|
||||||
object: T,
|
object: T,
|
||||||
|
@ -32,17 +33,16 @@ function getKeysInObject<T extends Record<string, unknown>>(
|
||||||
const ownPrefix = prefix ? `${prefix}.${key}` : key;
|
const ownPrefix = prefix ? `${prefix}.${key}` : key;
|
||||||
keys.push(ownPrefix);
|
keys.push(ownPrefix);
|
||||||
if (isPlainObject(object[key])) {
|
if (isPlainObject(object[key])) {
|
||||||
keys.push(
|
keys.push(...getKeysInObject(object[key] as Record<string, unknown>, ownPrefix));
|
||||||
...getKeysInObject(object[key] as Record<string, unknown>, ownPrefix)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addToContextWhenValidated<
|
function addToContextWhenValidated<T extends t.InterfaceType<any> | t.PartialType<any>>(
|
||||||
T extends t.InterfaceType<any> | t.PartialType<any>
|
type: T,
|
||||||
>(type: T, prefix: string): T {
|
prefix: string
|
||||||
|
): T {
|
||||||
const validate = (input: unknown, context: t.Context) => {
|
const validate = (input: unknown, context: t.Context) => {
|
||||||
const result = type.validate(input, context);
|
const result = type.validate(input, context);
|
||||||
const keysType = context[0].type as StrictKeysType;
|
const keysType = context[0].type as StrictKeysType;
|
||||||
|
@ -50,36 +50,19 @@ function addToContextWhenValidated<
|
||||||
throw new Error('Expected a top-level StrictKeysType');
|
throw new Error('Expected a top-level StrictKeysType');
|
||||||
}
|
}
|
||||||
if (isRight(result)) {
|
if (isRight(result)) {
|
||||||
keysType.trackedKeys.push(
|
keysType.trackedKeys.push(...Object.keys(type.props).map((propKey) => `${prefix}${propKey}`));
|
||||||
...Object.keys(type.props).map((propKey) => `${prefix}${propKey}`)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type._tag === 'InterfaceType') {
|
if (type._tag === 'InterfaceType') {
|
||||||
return new t.InterfaceType(
|
return new t.InterfaceType(type.name, type.is, validate, type.encode, type.props) as T;
|
||||||
type.name,
|
|
||||||
type.is,
|
|
||||||
validate,
|
|
||||||
type.encode,
|
|
||||||
type.props
|
|
||||||
) as T;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new t.PartialType(
|
return new t.PartialType(type.name, type.is, validate, type.encode, type.props) as T;
|
||||||
type.name,
|
|
||||||
type.is,
|
|
||||||
validate,
|
|
||||||
type.encode,
|
|
||||||
type.props
|
|
||||||
) as T;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function trackKeysOfValidatedTypes(
|
function trackKeysOfValidatedTypes(type: ParsableType | t.Any, prefix: string = ''): t.Any {
|
||||||
type: ParsableType | t.Any,
|
|
||||||
prefix: string = ''
|
|
||||||
): t.Any {
|
|
||||||
if (!('_tag' in type)) {
|
if (!('_tag' in type)) {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
@ -89,27 +72,24 @@ function trackKeysOfValidatedTypes(
|
||||||
case 'IntersectionType': {
|
case 'IntersectionType': {
|
||||||
const collectionType = type as t.IntersectionType<t.Any[]>;
|
const collectionType = type as t.IntersectionType<t.Any[]>;
|
||||||
return t.intersection(
|
return t.intersection(
|
||||||
collectionType.types.map((rt) =>
|
collectionType.types.map((rt) => trackKeysOfValidatedTypes(rt, prefix)) as [t.Any, t.Any]
|
||||||
trackKeysOfValidatedTypes(rt, prefix)
|
|
||||||
) as [t.Any, t.Any]
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'UnionType': {
|
case 'UnionType': {
|
||||||
const collectionType = type as t.UnionType<t.Any[]>;
|
const collectionType = type as t.UnionType<t.Any[]>;
|
||||||
return t.union(
|
return t.union(
|
||||||
collectionType.types.map((rt) =>
|
collectionType.types.map((rt) => trackKeysOfValidatedTypes(rt, prefix)) as [t.Any, t.Any]
|
||||||
trackKeysOfValidatedTypes(rt, prefix)
|
|
||||||
) as [t.Any, t.Any]
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'MergeType': {
|
case 'MergeType': {
|
||||||
const collectionType = type as MergeType<t.Any[]>;
|
const collectionType = type as MergeType<t.Any, t.Any>;
|
||||||
return merge(
|
return mergeRt(
|
||||||
collectionType.types.map((rt) =>
|
...(collectionType.types.map((rt) => trackKeysOfValidatedTypes(rt, prefix)) as [
|
||||||
trackKeysOfValidatedTypes(rt, prefix)
|
t.Any,
|
||||||
) as [t.Any, t.Any]
|
t.Any
|
||||||
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,9 +122,7 @@ function trackKeysOfValidatedTypes(
|
||||||
case 'ExactType': {
|
case 'ExactType': {
|
||||||
const exactType = type as t.ExactType<t.HasProps>;
|
const exactType = type as t.ExactType<t.HasProps>;
|
||||||
|
|
||||||
return t.exact(
|
return t.exact(trackKeysOfValidatedTypes(exactType.type, prefix) as t.HasProps);
|
||||||
trackKeysOfValidatedTypes(exactType.type, prefix) as t.HasProps
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -169,17 +147,11 @@ class StrictKeysType<
|
||||||
(input, context) => {
|
(input, context) => {
|
||||||
this.trackedKeys.length = 0;
|
this.trackedKeys.length = 0;
|
||||||
return either.chain(trackedType.validate(input, context), (i) => {
|
return either.chain(trackedType.validate(input, context), (i) => {
|
||||||
const originalKeys = getKeysInObject(
|
const originalKeys = getKeysInObject(input as Record<string, unknown>);
|
||||||
input as Record<string, unknown>
|
|
||||||
);
|
|
||||||
const excessKeys = difference(originalKeys, this.trackedKeys);
|
const excessKeys = difference(originalKeys, this.trackedKeys);
|
||||||
|
|
||||||
if (excessKeys.length) {
|
if (excessKeys.length) {
|
||||||
return t.failure(
|
return t.failure(i, context, `Excess keys are not allowed: \n${excessKeys.join('\n')}`);
|
||||||
i,
|
|
||||||
context,
|
|
||||||
`Excess keys are not allowed: \n${excessKeys.join('\n')}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return t.success(i);
|
return t.success(i);
|
19
packages/kbn-io-ts-utils/tsconfig.json
Normal file
19
packages/kbn-io-ts-utils/tsconfig.json
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"incremental": false,
|
||||||
|
"outDir": "./target",
|
||||||
|
"stripInternal": false,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"sourceRoot": "../../../../packages/kbn-io-ts-utils/src",
|
||||||
|
"types": [
|
||||||
|
"jest",
|
||||||
|
"node"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src/**/*.ts"
|
||||||
|
]
|
||||||
|
}
|
7
packages/kbn-server-route-repository/README.md
Normal file
7
packages/kbn-server-route-repository/README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# @kbn/server-route-repository
|
||||||
|
|
||||||
|
Utility functions for creating a typed server route repository, and a typed client, generating runtime validation and type validation from the same route definition.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
TBD
|
13
packages/kbn-server-route-repository/jest.config.js
Normal file
13
packages/kbn-server-route-repository/jest.config.js
Normal file
|
@ -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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
preset: '@kbn/test',
|
||||||
|
rootDir: '../..',
|
||||||
|
roots: ['<rootDir>/packages/kbn-server-route-repository'],
|
||||||
|
};
|
16
packages/kbn-server-route-repository/package.json
Normal file
16
packages/kbn-server-route-repository/package.json
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "@kbn/server-route-repository",
|
||||||
|
"main": "./target/index.js",
|
||||||
|
"types": "./target/index.d.ts",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "SSPL-1.0 OR Elastic License 2.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "../../node_modules/.bin/tsc",
|
||||||
|
"kbn:bootstrap": "yarn build",
|
||||||
|
"kbn:watch": "yarn build --watch"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@kbn/io-ts-utils": "link:../kbn-io-ts-utils"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
ServerRouteCreateOptions,
|
||||||
|
ServerRouteHandlerResources,
|
||||||
|
RouteParamsRT,
|
||||||
|
ServerRoute,
|
||||||
|
} from './typings';
|
||||||
|
|
||||||
|
export function createServerRouteFactory<
|
||||||
|
TRouteHandlerResources extends ServerRouteHandlerResources,
|
||||||
|
TRouteCreateOptions extends ServerRouteCreateOptions
|
||||||
|
>(): <
|
||||||
|
TEndpoint extends string,
|
||||||
|
TReturnType,
|
||||||
|
TRouteParamsRT extends RouteParamsRT | undefined = undefined
|
||||||
|
>(
|
||||||
|
route: ServerRoute<
|
||||||
|
TEndpoint,
|
||||||
|
TRouteParamsRT,
|
||||||
|
TRouteHandlerResources,
|
||||||
|
TReturnType,
|
||||||
|
TRouteCreateOptions
|
||||||
|
>
|
||||||
|
) => ServerRoute<
|
||||||
|
TEndpoint,
|
||||||
|
TRouteParamsRT,
|
||||||
|
TRouteHandlerResources,
|
||||||
|
TReturnType,
|
||||||
|
TRouteCreateOptions
|
||||||
|
> {
|
||||||
|
return (route) => route;
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
ServerRouteHandlerResources,
|
||||||
|
ServerRouteRepository,
|
||||||
|
ServerRouteCreateOptions,
|
||||||
|
} from './typings';
|
||||||
|
|
||||||
|
export function createServerRouteRepository<
|
||||||
|
TRouteHandlerResources extends ServerRouteHandlerResources = never,
|
||||||
|
TRouteCreateOptions extends ServerRouteCreateOptions = never
|
||||||
|
>(): ServerRouteRepository<TRouteHandlerResources, TRouteCreateOptions, {}> {
|
||||||
|
let routes: Record<string, any> = {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
add(route) {
|
||||||
|
routes = {
|
||||||
|
...routes,
|
||||||
|
[route.endpoint]: route,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this as any;
|
||||||
|
},
|
||||||
|
merge(repository) {
|
||||||
|
routes = {
|
||||||
|
...routes,
|
||||||
|
...Object.fromEntries(repository.getRoutes().map((route) => [route.endpoint, route])),
|
||||||
|
};
|
||||||
|
|
||||||
|
return this as any;
|
||||||
|
},
|
||||||
|
getRoutes: () => Object.values(routes),
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
import { jsonRt } from '@kbn/io-ts-utils';
|
||||||
|
import * as t from 'io-ts';
|
||||||
|
import { decodeRequestParams } from './decode_request_params';
|
||||||
|
|
||||||
|
describe('decodeRequestParams', () => {
|
||||||
|
it('decodes request params', () => {
|
||||||
|
const decode = () => {
|
||||||
|
return decodeRequestParams(
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
serviceName: 'opbeans-java',
|
||||||
|
},
|
||||||
|
body: null,
|
||||||
|
query: {
|
||||||
|
start: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
t.type({
|
||||||
|
path: t.type({
|
||||||
|
serviceName: t.string,
|
||||||
|
}),
|
||||||
|
query: t.type({
|
||||||
|
start: t.string,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
expect(decode).not.toThrow();
|
||||||
|
|
||||||
|
expect(decode()).toEqual({
|
||||||
|
path: {
|
||||||
|
serviceName: 'opbeans-java',
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
start: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails on excess keys', () => {
|
||||||
|
const decode = () => {
|
||||||
|
return decodeRequestParams(
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
serviceName: 'opbeans-java',
|
||||||
|
extraKey: '',
|
||||||
|
},
|
||||||
|
body: null,
|
||||||
|
query: {
|
||||||
|
start: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
t.type({
|
||||||
|
path: t.type({
|
||||||
|
serviceName: t.string,
|
||||||
|
}),
|
||||||
|
query: t.type({
|
||||||
|
start: t.string,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(decode).toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"Excess keys are not allowed:
|
||||||
|
path.extraKey"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the decoded output', () => {
|
||||||
|
const decode = () => {
|
||||||
|
return decodeRequestParams(
|
||||||
|
{
|
||||||
|
params: {},
|
||||||
|
query: {
|
||||||
|
_inspect: 'true',
|
||||||
|
},
|
||||||
|
body: null,
|
||||||
|
},
|
||||||
|
t.type({
|
||||||
|
query: t.type({
|
||||||
|
_inspect: jsonRt.pipe(t.boolean),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(decode).not.toThrow();
|
||||||
|
|
||||||
|
expect(decode()).toEqual({
|
||||||
|
query: {
|
||||||
|
_inspect: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('strips empty params', () => {
|
||||||
|
const decode = () => {
|
||||||
|
return decodeRequestParams(
|
||||||
|
{
|
||||||
|
params: {},
|
||||||
|
query: {},
|
||||||
|
body: {},
|
||||||
|
},
|
||||||
|
t.type({
|
||||||
|
body: t.any,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(decode).not.toThrow();
|
||||||
|
|
||||||
|
expect(decode()).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
import * as t from 'io-ts';
|
||||||
|
import { omitBy, isPlainObject, isEmpty } from 'lodash';
|
||||||
|
import { isLeft } from 'fp-ts/lib/Either';
|
||||||
|
import { PathReporter } from 'io-ts/lib/PathReporter';
|
||||||
|
import Boom from '@hapi/boom';
|
||||||
|
import { strictKeysRt } from '@kbn/io-ts-utils';
|
||||||
|
import { RouteParamsRT } from './typings';
|
||||||
|
|
||||||
|
interface KibanaRequestParams {
|
||||||
|
body: unknown;
|
||||||
|
query: unknown;
|
||||||
|
params: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeRequestParams<T extends RouteParamsRT>(
|
||||||
|
params: KibanaRequestParams,
|
||||||
|
paramsRt: T
|
||||||
|
): t.OutputOf<T> {
|
||||||
|
const paramMap = omitBy(
|
||||||
|
{
|
||||||
|
path: params.params,
|
||||||
|
body: params.body,
|
||||||
|
query: params.query,
|
||||||
|
},
|
||||||
|
(val) => val === null || val === undefined || (isPlainObject(val) && isEmpty(val))
|
||||||
|
);
|
||||||
|
|
||||||
|
// decode = validate
|
||||||
|
const result = strictKeysRt(paramsRt).decode(paramMap);
|
||||||
|
|
||||||
|
if (isLeft(result)) {
|
||||||
|
throw Boom.badRequest(PathReporter.report(result)[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.right;
|
||||||
|
}
|
20
packages/kbn-server-route-repository/src/format_request.ts
Normal file
20
packages/kbn-server-route-repository/src/format_request.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { parseEndpoint } from './parse_endpoint';
|
||||||
|
|
||||||
|
export function formatRequest(endpoint: string, pathParams: Record<string, any> = {}) {
|
||||||
|
const { method, pathname: rawPathname } = parseEndpoint(endpoint);
|
||||||
|
|
||||||
|
// replace template variables with path params
|
||||||
|
const pathname = Object.keys(pathParams).reduce((acc, paramName) => {
|
||||||
|
return acc.replace(`{${paramName}}`, pathParams[paramName]);
|
||||||
|
}, rawPathname);
|
||||||
|
|
||||||
|
return { method, pathname };
|
||||||
|
}
|
24
packages/kbn-server-route-repository/src/index.ts
Normal file
24
packages/kbn-server-route-repository/src/index.ts
Normal file
|
@ -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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { createServerRouteRepository } from './create_server_route_repository';
|
||||||
|
export { createServerRouteFactory } from './create_server_route_factory';
|
||||||
|
export { formatRequest } from './format_request';
|
||||||
|
export { parseEndpoint } from './parse_endpoint';
|
||||||
|
export { decodeRequestParams } from './decode_request_params';
|
||||||
|
export { routeValidationObject } from './route_validation_object';
|
||||||
|
export {
|
||||||
|
RouteRepositoryClient,
|
||||||
|
ReturnOf,
|
||||||
|
EndpointOf,
|
||||||
|
ClientRequestParamsOf,
|
||||||
|
DecodedRequestParamsOf,
|
||||||
|
ServerRouteRepository,
|
||||||
|
ServerRoute,
|
||||||
|
RouteParamsRT,
|
||||||
|
} from './typings';
|
22
packages/kbn-server-route-repository/src/parse_endpoint.ts
Normal file
22
packages/kbn-server-route-repository/src/parse_endpoint.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
type Method = 'get' | 'post' | 'put' | 'delete';
|
||||||
|
|
||||||
|
export function parseEndpoint(endpoint: string) {
|
||||||
|
const parts = endpoint.split(' ');
|
||||||
|
|
||||||
|
const method = parts[0].trim().toLowerCase() as Method;
|
||||||
|
const pathname = parts[1].trim();
|
||||||
|
|
||||||
|
if (!['get', 'post', 'put', 'delete'].includes(method)) {
|
||||||
|
throw new Error('Endpoint was not prefixed with a valid HTTP method');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { method, pathname };
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
import { schema } from '@kbn/config-schema';
|
||||||
|
|
||||||
|
const anyObject = schema.object({}, { unknowns: 'allow' });
|
||||||
|
|
||||||
|
export const routeValidationObject = {
|
||||||
|
// `body` can be null, but `validate` expects non-nullable types
|
||||||
|
// if any validation is defined. Not having validation currently
|
||||||
|
// means we don't get the payload. See
|
||||||
|
// https://github.com/elastic/kibana/issues/50179
|
||||||
|
body: schema.nullable(anyObject),
|
||||||
|
params: anyObject,
|
||||||
|
query: anyObject,
|
||||||
|
};
|
238
packages/kbn-server-route-repository/src/test_types.ts
Normal file
238
packages/kbn-server-route-repository/src/test_types.ts
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
/*
|
||||||
|
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
import * as t from 'io-ts';
|
||||||
|
import { createServerRouteRepository } from './create_server_route_repository';
|
||||||
|
import { decodeRequestParams } from './decode_request_params';
|
||||||
|
import { EndpointOf, ReturnOf, RouteRepositoryClient } from './typings';
|
||||||
|
|
||||||
|
function assertType<TShape = never>(value: TShape) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic arguments for createServerRouteRepository should be set,
|
||||||
|
// if not, registering routes should not be allowed
|
||||||
|
createServerRouteRepository().add({
|
||||||
|
// @ts-expect-error
|
||||||
|
endpoint: 'any_endpoint',
|
||||||
|
// @ts-expect-error
|
||||||
|
handler: async ({ params }) => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
// If a params codec is not set, its type should not be available in
|
||||||
|
// the request handler.
|
||||||
|
createServerRouteRepository<{}, {}>().add({
|
||||||
|
endpoint: 'endpoint_without_params',
|
||||||
|
handler: async (resources) => {
|
||||||
|
// @ts-expect-error Argument of type '{}' is not assignable to parameter of type '{ params: any; }'.
|
||||||
|
assertType<{ params: any }>(resources);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// If a params codec is set, its type _should_ be available in the
|
||||||
|
// request handler.
|
||||||
|
createServerRouteRepository<{}, {}>().add({
|
||||||
|
endpoint: 'endpoint_with_params',
|
||||||
|
params: t.type({
|
||||||
|
path: t.type({
|
||||||
|
serviceName: t.string,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
handler: async (resources) => {
|
||||||
|
assertType<{ params: { path: { serviceName: string } } }>(resources);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Resources should be passed to the request handler.
|
||||||
|
createServerRouteRepository<{ context: { getSpaceId: () => string } }, {}>().add({
|
||||||
|
endpoint: 'endpoint_with_params',
|
||||||
|
params: t.type({
|
||||||
|
path: t.type({
|
||||||
|
serviceName: t.string,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
handler: async ({ context }) => {
|
||||||
|
const spaceId = context.getSpaceId();
|
||||||
|
assertType<string>(spaceId);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create options are available when registering a route.
|
||||||
|
createServerRouteRepository<{}, { options: { tags: string[] } }>().add({
|
||||||
|
endpoint: 'endpoint_with_params',
|
||||||
|
params: t.type({
|
||||||
|
path: t.type({
|
||||||
|
serviceName: t.string,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
options: {
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
handler: async (resources) => {
|
||||||
|
assertType<{ params: { path: { serviceName: string } } }>(resources);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const repository = createServerRouteRepository<{}, {}>()
|
||||||
|
.add({
|
||||||
|
endpoint: 'endpoint_without_params',
|
||||||
|
handler: async () => {
|
||||||
|
return {
|
||||||
|
noParamsForMe: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.add({
|
||||||
|
endpoint: 'endpoint_with_params',
|
||||||
|
params: t.type({
|
||||||
|
path: t.type({
|
||||||
|
serviceName: t.string,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
handler: async () => {
|
||||||
|
return {
|
||||||
|
yesParamsForMe: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.add({
|
||||||
|
endpoint: 'endpoint_with_optional_params',
|
||||||
|
params: t.partial({
|
||||||
|
query: t.partial({
|
||||||
|
serviceName: t.string,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
handler: async () => {
|
||||||
|
return {
|
||||||
|
someParamsForMe: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
type TestRepository = typeof repository;
|
||||||
|
|
||||||
|
// EndpointOf should return all valid endpoints of a repository
|
||||||
|
|
||||||
|
assertType<Array<EndpointOf<TestRepository>>>([
|
||||||
|
'endpoint_with_params',
|
||||||
|
'endpoint_without_params',
|
||||||
|
'endpoint_with_optional_params',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// @ts-expect-error Type '"this_endpoint_does_not_exist"' is not assignable to type '"endpoint_without_params" | "endpoint_with_params" | "endpoint_with_optional_params"'
|
||||||
|
assertType<Array<EndpointOf<TestRepository>>>(['this_endpoint_does_not_exist']);
|
||||||
|
|
||||||
|
// ReturnOf should return the return type of a request handler.
|
||||||
|
|
||||||
|
assertType<ReturnOf<TestRepository, 'endpoint_without_params'>>({
|
||||||
|
noParamsForMe: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const noParamsInvalid: ReturnOf<TestRepository, 'endpoint_without_params'> = {
|
||||||
|
// @ts-expect-error type '{ paramsForMe: boolean; }' is not assignable to type '{ noParamsForMe: boolean; }'.
|
||||||
|
paramsForMe: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// RouteRepositoryClient
|
||||||
|
|
||||||
|
type TestClient = RouteRepositoryClient<TestRepository, { timeout: number }>;
|
||||||
|
|
||||||
|
const client: TestClient = {} as any;
|
||||||
|
|
||||||
|
// It should respect any additional create options.
|
||||||
|
|
||||||
|
// @ts-expect-error Property 'timeout' is missing
|
||||||
|
client({
|
||||||
|
endpoint: 'endpoint_without_params',
|
||||||
|
});
|
||||||
|
|
||||||
|
client({
|
||||||
|
endpoint: 'endpoint_without_params',
|
||||||
|
timeout: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
// It does not allow params for routes without a params codec
|
||||||
|
client({
|
||||||
|
endpoint: 'endpoint_without_params',
|
||||||
|
// @ts-expect-error Object literal may only specify known properties, and 'params' does not exist in type
|
||||||
|
params: {},
|
||||||
|
timeout: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
// It requires params for routes with a params codec
|
||||||
|
client({
|
||||||
|
endpoint: 'endpoint_with_params',
|
||||||
|
params: {
|
||||||
|
// @ts-expect-error property 'serviceName' is missing in type '{}'
|
||||||
|
path: {},
|
||||||
|
},
|
||||||
|
timeout: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Params are optional if the codec has no required keys
|
||||||
|
client({
|
||||||
|
endpoint: 'endpoint_with_optional_params',
|
||||||
|
timeout: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
// If optional, an error will still occur if the params do not match
|
||||||
|
client({
|
||||||
|
endpoint: 'endpoint_with_optional_params',
|
||||||
|
timeout: 1,
|
||||||
|
params: {
|
||||||
|
// @ts-expect-error Object literal may only specify known properties, and 'path' does not exist in type
|
||||||
|
path: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// The return type is correctly inferred
|
||||||
|
client({
|
||||||
|
endpoint: 'endpoint_with_params',
|
||||||
|
params: {
|
||||||
|
path: {
|
||||||
|
serviceName: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
timeout: 1,
|
||||||
|
}).then((res) => {
|
||||||
|
assertType<{
|
||||||
|
noParamsForMe: boolean;
|
||||||
|
// @ts-expect-error Property 'noParamsForMe' is missing in type
|
||||||
|
}>(res);
|
||||||
|
|
||||||
|
assertType<{
|
||||||
|
yesParamsForMe: boolean;
|
||||||
|
}>(res);
|
||||||
|
});
|
||||||
|
|
||||||
|
// decodeRequestParams should return the type of the codec that is passed
|
||||||
|
assertType<{ path: { serviceName: string } }>(
|
||||||
|
decodeRequestParams(
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
serviceName: 'serviceName',
|
||||||
|
},
|
||||||
|
body: undefined,
|
||||||
|
query: undefined,
|
||||||
|
},
|
||||||
|
t.type({ path: t.type({ serviceName: t.string }) })
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertType<{ path: { serviceName: boolean } }>(
|
||||||
|
// @ts-expect-error The types of 'path.serviceName' are incompatible between these types.
|
||||||
|
decodeRequestParams(
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
serviceName: 'serviceName',
|
||||||
|
},
|
||||||
|
body: undefined,
|
||||||
|
query: undefined,
|
||||||
|
},
|
||||||
|
t.type({ path: t.type({ serviceName: t.string }) })
|
||||||
|
)
|
||||||
|
);
|
192
packages/kbn-server-route-repository/src/typings.ts
Normal file
192
packages/kbn-server-route-repository/src/typings.ts
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
/*
|
||||||
|
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as t from 'io-ts';
|
||||||
|
import { RequiredKeys } from 'utility-types';
|
||||||
|
|
||||||
|
type MaybeOptional<T extends { params: Record<string, any> }> = RequiredKeys<
|
||||||
|
T['params']
|
||||||
|
> extends never
|
||||||
|
? { params?: T['params'] }
|
||||||
|
: { params: T['params'] };
|
||||||
|
|
||||||
|
type WithoutIncompatibleMethods<T extends t.Any> = Omit<T, 'encode' | 'asEncoder'> & {
|
||||||
|
encode: t.Encode<any, any>;
|
||||||
|
asEncoder: () => t.Encoder<any, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RouteParamsRT = WithoutIncompatibleMethods<
|
||||||
|
t.Type<{
|
||||||
|
path?: any;
|
||||||
|
query?: any;
|
||||||
|
body?: any;
|
||||||
|
}>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export interface RouteState {
|
||||||
|
[endpoint: string]: ServerRoute<any, any, any, any, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ServerRouteHandlerResources = Record<string, any>;
|
||||||
|
export type ServerRouteCreateOptions = Record<string, any>;
|
||||||
|
|
||||||
|
export type ServerRoute<
|
||||||
|
TEndpoint extends string,
|
||||||
|
TRouteParamsRT extends RouteParamsRT | undefined,
|
||||||
|
TRouteHandlerResources extends ServerRouteHandlerResources,
|
||||||
|
TReturnType,
|
||||||
|
TRouteCreateOptions extends ServerRouteCreateOptions
|
||||||
|
> = {
|
||||||
|
endpoint: TEndpoint;
|
||||||
|
params?: TRouteParamsRT;
|
||||||
|
handler: ({}: TRouteHandlerResources &
|
||||||
|
(TRouteParamsRT extends RouteParamsRT
|
||||||
|
? DecodedRequestParamsOfType<TRouteParamsRT>
|
||||||
|
: {})) => Promise<TReturnType>;
|
||||||
|
} & TRouteCreateOptions;
|
||||||
|
|
||||||
|
export interface ServerRouteRepository<
|
||||||
|
TRouteHandlerResources extends ServerRouteHandlerResources = ServerRouteHandlerResources,
|
||||||
|
TRouteCreateOptions extends ServerRouteCreateOptions = ServerRouteCreateOptions,
|
||||||
|
TRouteState extends RouteState = RouteState
|
||||||
|
> {
|
||||||
|
add<
|
||||||
|
TEndpoint extends string,
|
||||||
|
TReturnType,
|
||||||
|
TRouteParamsRT extends RouteParamsRT | undefined = undefined
|
||||||
|
>(
|
||||||
|
route: ServerRoute<
|
||||||
|
TEndpoint,
|
||||||
|
TRouteParamsRT,
|
||||||
|
TRouteHandlerResources,
|
||||||
|
TReturnType,
|
||||||
|
TRouteCreateOptions
|
||||||
|
>
|
||||||
|
): ServerRouteRepository<
|
||||||
|
TRouteHandlerResources,
|
||||||
|
TRouteCreateOptions,
|
||||||
|
TRouteState &
|
||||||
|
{
|
||||||
|
[key in TEndpoint]: ServerRoute<
|
||||||
|
TEndpoint,
|
||||||
|
TRouteParamsRT,
|
||||||
|
TRouteHandlerResources,
|
||||||
|
TReturnType,
|
||||||
|
TRouteCreateOptions
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
merge<
|
||||||
|
TServerRouteRepository extends ServerRouteRepository<
|
||||||
|
TRouteHandlerResources,
|
||||||
|
TRouteCreateOptions
|
||||||
|
>
|
||||||
|
>(
|
||||||
|
repository: TServerRouteRepository
|
||||||
|
): TServerRouteRepository extends ServerRouteRepository<
|
||||||
|
TRouteHandlerResources,
|
||||||
|
TRouteCreateOptions,
|
||||||
|
infer TRouteStateToMerge
|
||||||
|
>
|
||||||
|
? ServerRouteRepository<
|
||||||
|
TRouteHandlerResources,
|
||||||
|
TRouteCreateOptions,
|
||||||
|
TRouteState & TRouteStateToMerge
|
||||||
|
>
|
||||||
|
: never;
|
||||||
|
getRoutes: () => Array<
|
||||||
|
ServerRoute<string, RouteParamsRT, TRouteHandlerResources, unknown, TRouteCreateOptions>
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientRequestParamsOfType<
|
||||||
|
TRouteParamsRT extends RouteParamsRT
|
||||||
|
> = TRouteParamsRT extends t.Mixed
|
||||||
|
? MaybeOptional<{
|
||||||
|
params: t.OutputOf<TRouteParamsRT>;
|
||||||
|
}>
|
||||||
|
: {};
|
||||||
|
|
||||||
|
type DecodedRequestParamsOfType<
|
||||||
|
TRouteParamsRT extends RouteParamsRT
|
||||||
|
> = TRouteParamsRT extends t.Mixed
|
||||||
|
? MaybeOptional<{
|
||||||
|
params: t.TypeOf<TRouteParamsRT>;
|
||||||
|
}>
|
||||||
|
: {};
|
||||||
|
|
||||||
|
export type EndpointOf<
|
||||||
|
TServerRouteRepository extends ServerRouteRepository<any, any, any>
|
||||||
|
> = TServerRouteRepository extends ServerRouteRepository<any, any, infer TRouteState>
|
||||||
|
? keyof TRouteState
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type ReturnOf<
|
||||||
|
TServerRouteRepository extends ServerRouteRepository<any, any, any>,
|
||||||
|
TEndpoint extends EndpointOf<TServerRouteRepository>
|
||||||
|
> = TServerRouteRepository extends ServerRouteRepository<any, any, infer TRouteState>
|
||||||
|
? TEndpoint extends keyof TRouteState
|
||||||
|
? TRouteState[TEndpoint] extends ServerRoute<
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
infer TReturnType,
|
||||||
|
ServerRouteCreateOptions
|
||||||
|
>
|
||||||
|
? TReturnType
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type DecodedRequestParamsOf<
|
||||||
|
TServerRouteRepository extends ServerRouteRepository<any, any, any>,
|
||||||
|
TEndpoint extends EndpointOf<TServerRouteRepository>
|
||||||
|
> = TServerRouteRepository extends ServerRouteRepository<any, any, infer TRouteState>
|
||||||
|
? TEndpoint extends keyof TRouteState
|
||||||
|
? TRouteState[TEndpoint] extends ServerRoute<
|
||||||
|
any,
|
||||||
|
infer TRouteParamsRT,
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
ServerRouteCreateOptions
|
||||||
|
>
|
||||||
|
? TRouteParamsRT extends RouteParamsRT
|
||||||
|
? DecodedRequestParamsOfType<TRouteParamsRT>
|
||||||
|
: {}
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type ClientRequestParamsOf<
|
||||||
|
TServerRouteRepository extends ServerRouteRepository<any, any, any>,
|
||||||
|
TEndpoint extends EndpointOf<TServerRouteRepository>
|
||||||
|
> = TServerRouteRepository extends ServerRouteRepository<any, any, infer TRouteState>
|
||||||
|
? TEndpoint extends keyof TRouteState
|
||||||
|
? TRouteState[TEndpoint] extends ServerRoute<
|
||||||
|
any,
|
||||||
|
infer TRouteParamsRT,
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
ServerRouteCreateOptions
|
||||||
|
>
|
||||||
|
? TRouteParamsRT extends RouteParamsRT
|
||||||
|
? ClientRequestParamsOfType<TRouteParamsRT>
|
||||||
|
: {}
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type RouteRepositoryClient<
|
||||||
|
TServerRouteRepository extends ServerRouteRepository<any, any, any>,
|
||||||
|
TAdditionalClientOptions extends Record<string, any>
|
||||||
|
> = <TEndpoint extends EndpointOf<TServerRouteRepository>>(
|
||||||
|
options: {
|
||||||
|
endpoint: TEndpoint;
|
||||||
|
} & ClientRequestParamsOf<TServerRouteRepository, TEndpoint> &
|
||||||
|
TAdditionalClientOptions
|
||||||
|
) => Promise<ReturnOf<TServerRouteRepository, TEndpoint>>;
|
20
packages/kbn-server-route-repository/tsconfig.json
Normal file
20
packages/kbn-server-route-repository/tsconfig.json
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"incremental": false,
|
||||||
|
"outDir": "./target",
|
||||||
|
"stripInternal": false,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"sourceRoot": "../../../../packages/kbn-server-route-repository/src",
|
||||||
|
"types": [
|
||||||
|
"jest",
|
||||||
|
"node"
|
||||||
|
],
|
||||||
|
"noUnusedLocals": false
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src/**/*.ts"
|
||||||
|
]
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ export enum LatencyAggregationType {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const latencyAggregationTypeRt = t.union([
|
export const latencyAggregationTypeRt = t.union([
|
||||||
t.literal('avg'),
|
t.literal(LatencyAggregationType.avg),
|
||||||
t.literal('p95'),
|
t.literal(LatencyAggregationType.p95),
|
||||||
t.literal('p99'),
|
t.literal(LatencyAggregationType.p99),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -21,8 +21,5 @@ export const isoToEpochRt = new t.Type<number, string, unknown>(
|
||||||
? t.failure(input, context)
|
? t.failure(input, context)
|
||||||
: t.success(epochDate);
|
: t.success(epochDate);
|
||||||
}),
|
}),
|
||||||
(a) => {
|
(output) => new Date(output).toISOString()
|
||||||
const d = new Date(a);
|
|
||||||
return d.toISOString();
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,15 +14,18 @@ import {
|
||||||
act,
|
act,
|
||||||
waitFor,
|
waitFor,
|
||||||
} from '@testing-library/react';
|
} from '@testing-library/react';
|
||||||
import * as apmApi from '../../../../../../services/rest/createCallApmApi';
|
import {
|
||||||
|
getCallApmApiSpy,
|
||||||
|
CallApmApiSpy,
|
||||||
|
} from '../../../../../../services/rest/callApmApiSpy';
|
||||||
|
|
||||||
export const removeExternalLinkText = (str: string) =>
|
export const removeExternalLinkText = (str: string) =>
|
||||||
str.replace(/\(opens in a new tab or window\)/g, '');
|
str.replace(/\(opens in a new tab or window\)/g, '');
|
||||||
|
|
||||||
describe('LinkPreview', () => {
|
describe('LinkPreview', () => {
|
||||||
let callApmApiSpy: jest.SpyInstance<any, any>;
|
let callApmApiSpy: CallApmApiSpy;
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
callApmApiSpy = jest.spyOn(apmApi, 'callApmApi').mockResolvedValue({
|
callApmApiSpy = getCallApmApiSpy().mockResolvedValue({
|
||||||
transaction: { id: 'foo' },
|
transaction: { id: 'foo' },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import { fireEvent, render, RenderResult } from '@testing-library/react';
|
import { fireEvent, render, RenderResult } from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { getCallApmApiSpy } from '../../../../../services/rest/callApmApiSpy';
|
||||||
import { CustomLinkOverview } from '.';
|
import { CustomLinkOverview } from '.';
|
||||||
import { License } from '../../../../../../../licensing/common/license';
|
import { License } from '../../../../../../../licensing/common/license';
|
||||||
import { ApmPluginContextValue } from '../../../../../context/apm_plugin/apm_plugin_context';
|
import { ApmPluginContextValue } from '../../../../../context/apm_plugin/apm_plugin_context';
|
||||||
|
@ -17,7 +18,6 @@ import {
|
||||||
} from '../../../../../context/apm_plugin/mock_apm_plugin_context';
|
} from '../../../../../context/apm_plugin/mock_apm_plugin_context';
|
||||||
import { LicenseContext } from '../../../../../context/license/license_context';
|
import { LicenseContext } from '../../../../../context/license/license_context';
|
||||||
import * as hooks from '../../../../../hooks/use_fetcher';
|
import * as hooks from '../../../../../hooks/use_fetcher';
|
||||||
import * as apmApi from '../../../../../services/rest/createCallApmApi';
|
|
||||||
import {
|
import {
|
||||||
expectTextsInDocument,
|
expectTextsInDocument,
|
||||||
expectTextsNotInDocument,
|
expectTextsNotInDocument,
|
||||||
|
@ -43,7 +43,7 @@ function getMockAPMContext({ canSave }: { canSave: boolean }) {
|
||||||
|
|
||||||
describe('CustomLink', () => {
|
describe('CustomLink', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
jest.spyOn(apmApi, 'callApmApi').mockResolvedValue({});
|
getCallApmApiSpy().mockResolvedValue({});
|
||||||
});
|
});
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
|
|
|
@ -22,9 +22,12 @@ import * as useTransactionBreakdownHooks from '../../shared/charts/transaction_b
|
||||||
import { renderWithTheme } from '../../../utils/testHelpers';
|
import { renderWithTheme } from '../../../utils/testHelpers';
|
||||||
import { ServiceOverview } from './';
|
import { ServiceOverview } from './';
|
||||||
import { waitFor } from '@testing-library/dom';
|
import { waitFor } from '@testing-library/dom';
|
||||||
import * as callApmApiModule from '../../../services/rest/createCallApmApi';
|
|
||||||
import * as useApmServiceContextHooks from '../../../context/apm_service/use_apm_service_context';
|
import * as useApmServiceContextHooks from '../../../context/apm_service/use_apm_service_context';
|
||||||
import { LatencyAggregationType } from '../../../../common/latency_aggregation_types';
|
import { LatencyAggregationType } from '../../../../common/latency_aggregation_types';
|
||||||
|
import {
|
||||||
|
getCallApmApiSpy,
|
||||||
|
getCreateCallApmApiSpy,
|
||||||
|
} from '../../../services/rest/callApmApiSpy';
|
||||||
|
|
||||||
const KibanaReactContext = createKibanaReactContext({
|
const KibanaReactContext = createKibanaReactContext({
|
||||||
usageCollection: { reportUiCounter: () => {} },
|
usageCollection: { reportUiCounter: () => {} },
|
||||||
|
@ -83,10 +86,10 @@ describe('ServiceOverview', () => {
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
const calls = {
|
const calls = {
|
||||||
'GET /api/apm/services/{serviceName}/error_groups/primary_statistics': {
|
'GET /api/apm/services/{serviceName}/error_groups/primary_statistics': {
|
||||||
error_groups: [],
|
error_groups: [] as any[],
|
||||||
},
|
},
|
||||||
'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics': {
|
'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics': {
|
||||||
transactionGroups: [],
|
transactionGroups: [] as any[],
|
||||||
totalTransactionGroups: 0,
|
totalTransactionGroups: 0,
|
||||||
isAggregationAccurate: true,
|
isAggregationAccurate: true,
|
||||||
},
|
},
|
||||||
|
@ -95,19 +98,17 @@ describe('ServiceOverview', () => {
|
||||||
};
|
};
|
||||||
/* eslint-enable @typescript-eslint/naming-convention */
|
/* eslint-enable @typescript-eslint/naming-convention */
|
||||||
|
|
||||||
jest
|
const callApmApiSpy = getCallApmApiSpy().mockImplementation(
|
||||||
.spyOn(callApmApiModule, 'createCallApmApi')
|
({ endpoint }) => {
|
||||||
.mockImplementation(() => {});
|
|
||||||
|
|
||||||
const callApmApi = jest
|
|
||||||
.spyOn(callApmApiModule, 'callApmApi')
|
|
||||||
.mockImplementation(({ endpoint }) => {
|
|
||||||
const response = calls[endpoint as keyof typeof calls];
|
const response = calls[endpoint as keyof typeof calls];
|
||||||
|
|
||||||
return response
|
return response
|
||||||
? Promise.resolve(response)
|
? Promise.resolve(response)
|
||||||
: Promise.reject(`Response for ${endpoint} is not defined`);
|
: Promise.reject(`Response for ${endpoint} is not defined`);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
getCreateCallApmApiSpy().mockImplementation(() => callApmApiSpy as any);
|
||||||
jest
|
jest
|
||||||
.spyOn(useTransactionBreakdownHooks, 'useTransactionBreakdown')
|
.spyOn(useTransactionBreakdownHooks, 'useTransactionBreakdown')
|
||||||
.mockReturnValue({
|
.mockReturnValue({
|
||||||
|
@ -124,7 +125,7 @@ describe('ServiceOverview', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(callApmApi).toHaveBeenCalledTimes(Object.keys(calls).length)
|
expect(callApmApiSpy).toHaveBeenCalledTimes(Object.keys(calls).length)
|
||||||
);
|
);
|
||||||
|
|
||||||
expect((await findAllByText('Latency')).length).toBeGreaterThan(0);
|
expect((await findAllByText('Latency')).length).toBeGreaterThan(0);
|
||||||
|
|
|
@ -10,10 +10,10 @@ import {
|
||||||
fetchObservabilityOverviewPageData,
|
fetchObservabilityOverviewPageData,
|
||||||
getHasData,
|
getHasData,
|
||||||
} from './apm_observability_overview_fetchers';
|
} from './apm_observability_overview_fetchers';
|
||||||
import * as createCallApmApi from './createCallApmApi';
|
import { getCallApmApiSpy } from './callApmApiSpy';
|
||||||
|
|
||||||
describe('Observability dashboard data', () => {
|
describe('Observability dashboard data', () => {
|
||||||
const callApmApiMock = jest.spyOn(createCallApmApi, 'callApmApi');
|
const callApmApiMock = getCallApmApiSpy();
|
||||||
const params = {
|
const params = {
|
||||||
absoluteTime: {
|
absoluteTime: {
|
||||||
start: moment('2020-07-02T13:25:11.629Z').valueOf(),
|
start: moment('2020-07-02T13:25:11.629Z').valueOf(),
|
||||||
|
@ -84,7 +84,7 @@ describe('Observability dashboard data', () => {
|
||||||
callApmApiMock.mockImplementation(() =>
|
callApmApiMock.mockImplementation(() =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
serviceCount: 0,
|
serviceCount: 0,
|
||||||
transactionPerMinute: { value: null, timeseries: [] },
|
transactionPerMinute: { value: null, timeseries: [] as any },
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const response = await fetchObservabilityOverviewPageData(params);
|
const response = await fetchObservabilityOverviewPageData(params);
|
||||||
|
|
24
x-pack/plugins/apm/public/services/rest/callApmApiSpy.ts
Normal file
24
x-pack/plugins/apm/public/services/rest/callApmApiSpy.ts
Normal file
|
@ -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 * as createCallApmApi from './createCallApmApi';
|
||||||
|
import type { AbstractAPMClient } from './createCallApmApi';
|
||||||
|
|
||||||
|
export type CallApmApiSpy = jest.SpyInstance<
|
||||||
|
Promise<any>,
|
||||||
|
Parameters<AbstractAPMClient>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type CreateCallApmApiSpy = jest.SpyInstance<AbstractAPMClient>;
|
||||||
|
|
||||||
|
export const getCreateCallApmApiSpy = () =>
|
||||||
|
(jest.spyOn(
|
||||||
|
createCallApmApi,
|
||||||
|
'createCallApmApi'
|
||||||
|
) as unknown) as CreateCallApmApiSpy;
|
||||||
|
export const getCallApmApiSpy = () =>
|
||||||
|
(jest.spyOn(createCallApmApi, 'callApmApi') as unknown) as CallApmApiSpy;
|
|
@ -6,30 +6,68 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CoreSetup, CoreStart } from 'kibana/public';
|
import { CoreSetup, CoreStart } from 'kibana/public';
|
||||||
import { parseEndpoint } from '../../../common/apm_api/parse_endpoint';
|
import * as t from 'io-ts';
|
||||||
|
import type {
|
||||||
|
ClientRequestParamsOf,
|
||||||
|
EndpointOf,
|
||||||
|
ReturnOf,
|
||||||
|
RouteRepositoryClient,
|
||||||
|
ServerRouteRepository,
|
||||||
|
ServerRoute,
|
||||||
|
} from '@kbn/server-route-repository';
|
||||||
|
import { formatRequest } from '@kbn/server-route-repository/target/format_request';
|
||||||
import { FetchOptions } from '../../../common/fetch_options';
|
import { FetchOptions } from '../../../common/fetch_options';
|
||||||
import { callApi } from './callApi';
|
import { callApi } from './callApi';
|
||||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
import type {
|
||||||
import type { APMAPI } from '../../../server/routes/create_apm_api';
|
APMServerRouteRepository,
|
||||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
InspectResponse,
|
||||||
import type { Client } from '../../../server/routes/typings';
|
APMRouteHandlerResources,
|
||||||
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
export type APMClient = Client<APMAPI['_S']>;
|
} from '../../../server';
|
||||||
export type AutoAbortedAPMClient = Client<APMAPI['_S'], { abortable: false }>;
|
|
||||||
|
|
||||||
export type APMClientOptions = Omit<
|
export type APMClientOptions = Omit<
|
||||||
FetchOptions,
|
FetchOptions,
|
||||||
'query' | 'body' | 'pathname' | 'signal'
|
'query' | 'body' | 'pathname' | 'signal'
|
||||||
> & {
|
> & {
|
||||||
endpoint: string;
|
|
||||||
signal: AbortSignal | null;
|
signal: AbortSignal | null;
|
||||||
params?: {
|
|
||||||
body?: any;
|
|
||||||
query?: Record<string, any>;
|
|
||||||
path?: Record<string, any>;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type APMClient = RouteRepositoryClient<
|
||||||
|
APMServerRouteRepository,
|
||||||
|
APMClientOptions
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type AutoAbortedAPMClient = RouteRepositoryClient<
|
||||||
|
APMServerRouteRepository,
|
||||||
|
Omit<APMClientOptions, 'signal'>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type APIReturnType<
|
||||||
|
TEndpoint extends EndpointOf<APMServerRouteRepository>
|
||||||
|
> = ReturnOf<APMServerRouteRepository, TEndpoint> & {
|
||||||
|
_inspect?: InspectResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type APIEndpoint = EndpointOf<APMServerRouteRepository>;
|
||||||
|
|
||||||
|
export type APIClientRequestParamsOf<
|
||||||
|
TEndpoint extends EndpointOf<APMServerRouteRepository>
|
||||||
|
> = ClientRequestParamsOf<APMServerRouteRepository, TEndpoint>;
|
||||||
|
|
||||||
|
export type AbstractAPMRepository = ServerRouteRepository<
|
||||||
|
APMRouteHandlerResources,
|
||||||
|
{},
|
||||||
|
Record<
|
||||||
|
string,
|
||||||
|
ServerRoute<string, t.Mixed | undefined, APMRouteHandlerResources, any, {}>
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type AbstractAPMClient = RouteRepositoryClient<
|
||||||
|
AbstractAPMRepository,
|
||||||
|
APMClientOptions
|
||||||
|
>;
|
||||||
|
|
||||||
export let callApmApi: APMClient = () => {
|
export let callApmApi: APMClient = () => {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'callApmApi has to be initialized before used. Call createCallApmApi first.'
|
'callApmApi has to be initialized before used. Call createCallApmApi first.'
|
||||||
|
@ -37,9 +75,13 @@ export let callApmApi: APMClient = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createCallApmApi(core: CoreStart | CoreSetup) {
|
export function createCallApmApi(core: CoreStart | CoreSetup) {
|
||||||
callApmApi = ((options: APMClientOptions) => {
|
callApmApi = ((options) => {
|
||||||
const { endpoint, params, ...opts } = options;
|
const { endpoint, ...opts } = options;
|
||||||
const { method, pathname } = parseEndpoint(endpoint, params?.path);
|
const { params } = (options as unknown) as {
|
||||||
|
params?: Partial<Record<string, any>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { method, pathname } = formatRequest(endpoint, params?.path);
|
||||||
|
|
||||||
return callApi(core, {
|
return callApi(core, {
|
||||||
...opts,
|
...opts,
|
||||||
|
@ -50,10 +92,3 @@ export function createCallApmApi(core: CoreStart | CoreSetup) {
|
||||||
});
|
});
|
||||||
}) as APMClient;
|
}) as APMClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
// infer return type from API
|
|
||||||
export type APIReturnType<
|
|
||||||
TPath extends keyof APMAPI['_S']
|
|
||||||
> = APMAPI['_S'][TPath] extends { ret: any }
|
|
||||||
? APMAPI['_S'][TPath]['ret']
|
|
||||||
: unknown;
|
|
||||||
|
|
|
@ -120,5 +120,9 @@ export function mergeConfigs(
|
||||||
export const plugin = (initContext: PluginInitializerContext) =>
|
export const plugin = (initContext: PluginInitializerContext) =>
|
||||||
new APMPlugin(initContext);
|
new APMPlugin(initContext);
|
||||||
|
|
||||||
export { APMPlugin, APMPluginSetup } from './plugin';
|
export { APMPlugin } from './plugin';
|
||||||
|
export { APMPluginSetup } from './types';
|
||||||
|
export { APMServerRouteRepository } from './routes/get_global_apm_server_route_repository';
|
||||||
|
export { InspectResponse, APMRouteHandlerResources } from './routes/typings';
|
||||||
|
|
||||||
export type { ProcessorEvent } from '../common/processor_event';
|
export type { ProcessorEvent } from '../common/processor_event';
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { KibanaRequest } from '../../../../../../../src/core/server';
|
import { KibanaRequest } from '../../../../../../../src/core/server';
|
||||||
import { inspectableEsQueriesMap } from '../../../routes/create_api';
|
import { inspectableEsQueriesMap } from '../../../routes/register_routes';
|
||||||
|
|
||||||
function formatObj(obj: Record<string, any>) {
|
function formatObj(obj: Record<string, any>) {
|
||||||
return JSON.stringify(obj, null, 2);
|
return JSON.stringify(obj, null, 2);
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { KibanaRequest } from 'src/core/server';
|
|
||||||
import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport';
|
import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport';
|
||||||
import {
|
import {
|
||||||
CreateIndexRequest,
|
CreateIndexRequest,
|
||||||
|
@ -13,7 +12,7 @@ import {
|
||||||
IndexRequest,
|
IndexRequest,
|
||||||
} from '@elastic/elasticsearch/api/types';
|
} from '@elastic/elasticsearch/api/types';
|
||||||
import { unwrapEsResponse } from '../../../../../../observability/server';
|
import { unwrapEsResponse } from '../../../../../../observability/server';
|
||||||
import { APMRequestHandlerContext } from '../../../../routes/typings';
|
import { APMRouteHandlerResources } from '../../../../routes/typings';
|
||||||
import {
|
import {
|
||||||
ESSearchResponse,
|
ESSearchResponse,
|
||||||
ESSearchRequest,
|
ESSearchRequest,
|
||||||
|
@ -31,11 +30,9 @@ export type APMInternalClient = ReturnType<typeof createInternalESClient>;
|
||||||
|
|
||||||
export function createInternalESClient({
|
export function createInternalESClient({
|
||||||
context,
|
context,
|
||||||
|
debug,
|
||||||
request,
|
request,
|
||||||
}: {
|
}: Pick<APMRouteHandlerResources, 'context' | 'request'> & { debug: boolean }) {
|
||||||
context: APMRequestHandlerContext;
|
|
||||||
request: KibanaRequest;
|
|
||||||
}) {
|
|
||||||
const { asInternalUser } = context.core.elasticsearch.client;
|
const { asInternalUser } = context.core.elasticsearch.client;
|
||||||
|
|
||||||
function callEs<T extends { body: any }>({
|
function callEs<T extends { body: any }>({
|
||||||
|
@ -53,7 +50,7 @@ export function createInternalESClient({
|
||||||
title: getDebugTitle(request),
|
title: getDebugTitle(request),
|
||||||
body: getDebugBody(params, requestType),
|
body: getDebugBody(params, requestType),
|
||||||
}),
|
}),
|
||||||
debug: context.params.query._inspect,
|
debug,
|
||||||
isCalledWithInternalUser: true,
|
isCalledWithInternalUser: true,
|
||||||
request,
|
request,
|
||||||
requestType,
|
requestType,
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
|
|
||||||
import { setupRequest } from './setup_request';
|
import { setupRequest } from './setup_request';
|
||||||
import { APMConfig } from '../..';
|
import { APMConfig } from '../..';
|
||||||
import { APMRequestHandlerContext } from '../../routes/typings';
|
import { APMRouteHandlerResources } from '../../routes/typings';
|
||||||
import { KibanaRequest } from '../../../../../../src/core/server';
|
|
||||||
import { ProcessorEvent } from '../../../common/processor_event';
|
import { ProcessorEvent } from '../../../common/processor_event';
|
||||||
import { PROCESSOR_EVENT } from '../../../common/elasticsearch_fieldnames';
|
import { PROCESSOR_EVENT } from '../../../common/elasticsearch_fieldnames';
|
||||||
|
|
||||||
|
@ -32,7 +31,7 @@ jest.mock('../index_pattern/get_dynamic_index_pattern', () => ({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function getMockRequest() {
|
function getMockResources() {
|
||||||
const esClientMock = {
|
const esClientMock = {
|
||||||
asCurrentUser: {
|
asCurrentUser: {
|
||||||
search: jest.fn().mockResolvedValue({ body: {} }),
|
search: jest.fn().mockResolvedValue({ body: {} }),
|
||||||
|
@ -42,7 +41,7 @@ function getMockRequest() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockContext = ({
|
const mockResources = ({
|
||||||
config: new Proxy(
|
config: new Proxy(
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
|
@ -54,65 +53,69 @@ function getMockRequest() {
|
||||||
_inspect: false,
|
_inspect: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
core: {
|
context: {
|
||||||
elasticsearch: {
|
core: {
|
||||||
client: esClientMock,
|
elasticsearch: {
|
||||||
},
|
client: esClientMock,
|
||||||
uiSettings: {
|
|
||||||
client: {
|
|
||||||
get: jest.fn().mockResolvedValue(false),
|
|
||||||
},
|
},
|
||||||
},
|
uiSettings: {
|
||||||
savedObjects: {
|
client: {
|
||||||
client: {
|
get: jest.fn().mockResolvedValue(false),
|
||||||
get: jest.fn(),
|
},
|
||||||
|
},
|
||||||
|
savedObjects: {
|
||||||
|
client: {
|
||||||
|
get: jest.fn(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
ml: undefined,
|
ml: undefined,
|
||||||
},
|
},
|
||||||
} as unknown) as APMRequestHandlerContext & {
|
request: {
|
||||||
core: {
|
url: '',
|
||||||
elasticsearch: {
|
events: {
|
||||||
client: typeof esClientMock;
|
aborted$: {
|
||||||
};
|
subscribe: jest.fn().mockReturnValue({ unsubscribe: jest.fn() }),
|
||||||
uiSettings: {
|
},
|
||||||
client: {
|
},
|
||||||
get: jest.Mock<any, any>;
|
},
|
||||||
|
} as unknown) as APMRouteHandlerResources & {
|
||||||
|
context: {
|
||||||
|
core: {
|
||||||
|
elasticsearch: {
|
||||||
|
client: typeof esClientMock;
|
||||||
};
|
};
|
||||||
};
|
uiSettings: {
|
||||||
savedObjects: {
|
client: {
|
||||||
client: {
|
get: jest.Mock<any, any>;
|
||||||
get: jest.Mock<any, any>;
|
};
|
||||||
|
};
|
||||||
|
savedObjects: {
|
||||||
|
client: {
|
||||||
|
get: jest.Mock<any, any>;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockRequest = ({
|
return mockResources;
|
||||||
url: '',
|
|
||||||
events: {
|
|
||||||
aborted$: {
|
|
||||||
subscribe: jest.fn().mockReturnValue({ unsubscribe: jest.fn() }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as unknown) as KibanaRequest;
|
|
||||||
|
|
||||||
return { mockContext, mockRequest };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('setupRequest', () => {
|
describe('setupRequest', () => {
|
||||||
describe('with default args', () => {
|
describe('with default args', () => {
|
||||||
it('calls callWithRequest', async () => {
|
it('calls callWithRequest', async () => {
|
||||||
const { mockContext, mockRequest } = getMockRequest();
|
const mockResources = getMockResources();
|
||||||
const { apmEventClient } = await setupRequest(mockContext, mockRequest);
|
const { apmEventClient } = await setupRequest(mockResources);
|
||||||
await apmEventClient.search({
|
await apmEventClient.search({
|
||||||
apm: { events: [ProcessorEvent.transaction] },
|
apm: { events: [ProcessorEvent.transaction] },
|
||||||
body: { foo: 'bar' },
|
body: { foo: 'bar' },
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
mockContext.core.elasticsearch.client.asCurrentUser.search
|
mockResources.context.core.elasticsearch.client.asCurrentUser.search
|
||||||
).toHaveBeenCalledWith({
|
).toHaveBeenCalledWith({
|
||||||
index: ['apm-*'],
|
index: ['apm-*'],
|
||||||
body: {
|
body: {
|
||||||
|
@ -132,14 +135,14 @@ describe('setupRequest', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls callWithInternalUser', async () => {
|
it('calls callWithInternalUser', async () => {
|
||||||
const { mockContext, mockRequest } = getMockRequest();
|
const mockResources = getMockResources();
|
||||||
const { internalClient } = await setupRequest(mockContext, mockRequest);
|
const { internalClient } = await setupRequest(mockResources);
|
||||||
await internalClient.search({
|
await internalClient.search({
|
||||||
index: ['apm-*'],
|
index: ['apm-*'],
|
||||||
body: { foo: 'bar' },
|
body: { foo: 'bar' },
|
||||||
} as any);
|
} as any);
|
||||||
expect(
|
expect(
|
||||||
mockContext.core.elasticsearch.client.asInternalUser.search
|
mockResources.context.core.elasticsearch.client.asInternalUser.search
|
||||||
).toHaveBeenCalledWith({
|
).toHaveBeenCalledWith({
|
||||||
index: ['apm-*'],
|
index: ['apm-*'],
|
||||||
body: {
|
body: {
|
||||||
|
@ -151,8 +154,8 @@ describe('setupRequest', () => {
|
||||||
|
|
||||||
describe('with a bool filter', () => {
|
describe('with a bool filter', () => {
|
||||||
it('adds a range filter for `observer.version_major` to the existing filter', async () => {
|
it('adds a range filter for `observer.version_major` to the existing filter', async () => {
|
||||||
const { mockContext, mockRequest } = getMockRequest();
|
const mockResources = getMockResources();
|
||||||
const { apmEventClient } = await setupRequest(mockContext, mockRequest);
|
const { apmEventClient } = await setupRequest(mockResources);
|
||||||
await apmEventClient.search({
|
await apmEventClient.search({
|
||||||
apm: {
|
apm: {
|
||||||
events: [ProcessorEvent.transaction],
|
events: [ProcessorEvent.transaction],
|
||||||
|
@ -162,8 +165,8 @@ describe('setupRequest', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const params =
|
const params =
|
||||||
mockContext.core.elasticsearch.client.asCurrentUser.search.mock
|
mockResources.context.core.elasticsearch.client.asCurrentUser.search
|
||||||
.calls[0][0];
|
.mock.calls[0][0];
|
||||||
expect(params.body).toEqual({
|
expect(params.body).toEqual({
|
||||||
query: {
|
query: {
|
||||||
bool: {
|
bool: {
|
||||||
|
@ -178,8 +181,8 @@ describe('setupRequest', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not add a range filter for `observer.version_major` if includeLegacyData=true', async () => {
|
it('does not add a range filter for `observer.version_major` if includeLegacyData=true', async () => {
|
||||||
const { mockContext, mockRequest } = getMockRequest();
|
const mockResources = getMockResources();
|
||||||
const { apmEventClient } = await setupRequest(mockContext, mockRequest);
|
const { apmEventClient } = await setupRequest(mockResources);
|
||||||
await apmEventClient.search(
|
await apmEventClient.search(
|
||||||
{
|
{
|
||||||
apm: {
|
apm: {
|
||||||
|
@ -194,8 +197,8 @@ describe('setupRequest', () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const params =
|
const params =
|
||||||
mockContext.core.elasticsearch.client.asCurrentUser.search.mock
|
mockResources.context.core.elasticsearch.client.asCurrentUser.search
|
||||||
.calls[0][0];
|
.mock.calls[0][0];
|
||||||
expect(params.body).toEqual({
|
expect(params.body).toEqual({
|
||||||
query: {
|
query: {
|
||||||
bool: {
|
bool: {
|
||||||
|
@ -216,15 +219,15 @@ describe('setupRequest', () => {
|
||||||
|
|
||||||
describe('without a bool filter', () => {
|
describe('without a bool filter', () => {
|
||||||
it('adds a range filter for `observer.version_major`', async () => {
|
it('adds a range filter for `observer.version_major`', async () => {
|
||||||
const { mockContext, mockRequest } = getMockRequest();
|
const mockResources = getMockResources();
|
||||||
const { apmEventClient } = await setupRequest(mockContext, mockRequest);
|
const { apmEventClient } = await setupRequest(mockResources);
|
||||||
await apmEventClient.search({
|
await apmEventClient.search({
|
||||||
apm: {
|
apm: {
|
||||||
events: [ProcessorEvent.error],
|
events: [ProcessorEvent.error],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const params =
|
const params =
|
||||||
mockContext.core.elasticsearch.client.asCurrentUser.search.mock
|
mockResources.context.core.elasticsearch.client.asCurrentUser.search.mock
|
||||||
.calls[0][0];
|
.calls[0][0];
|
||||||
expect(params.body).toEqual({
|
expect(params.body).toEqual({
|
||||||
query: {
|
query: {
|
||||||
|
@ -241,12 +244,12 @@ describe('without a bool filter', () => {
|
||||||
|
|
||||||
describe('with includeFrozen=false', () => {
|
describe('with includeFrozen=false', () => {
|
||||||
it('sets `ignore_throttled=true`', async () => {
|
it('sets `ignore_throttled=true`', async () => {
|
||||||
const { mockContext, mockRequest } = getMockRequest();
|
const mockResources = getMockResources();
|
||||||
|
|
||||||
// mock includeFrozen to return false
|
// mock includeFrozen to return false
|
||||||
mockContext.core.uiSettings.client.get.mockResolvedValue(false);
|
mockResources.context.core.uiSettings.client.get.mockResolvedValue(false);
|
||||||
|
|
||||||
const { apmEventClient } = await setupRequest(mockContext, mockRequest);
|
const { apmEventClient } = await setupRequest(mockResources);
|
||||||
|
|
||||||
await apmEventClient.search({
|
await apmEventClient.search({
|
||||||
apm: {
|
apm: {
|
||||||
|
@ -255,7 +258,7 @@ describe('with includeFrozen=false', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const params =
|
const params =
|
||||||
mockContext.core.elasticsearch.client.asCurrentUser.search.mock
|
mockResources.context.core.elasticsearch.client.asCurrentUser.search.mock
|
||||||
.calls[0][0];
|
.calls[0][0];
|
||||||
expect(params.ignore_throttled).toBe(true);
|
expect(params.ignore_throttled).toBe(true);
|
||||||
});
|
});
|
||||||
|
@ -263,19 +266,19 @@ describe('with includeFrozen=false', () => {
|
||||||
|
|
||||||
describe('with includeFrozen=true', () => {
|
describe('with includeFrozen=true', () => {
|
||||||
it('sets `ignore_throttled=false`', async () => {
|
it('sets `ignore_throttled=false`', async () => {
|
||||||
const { mockContext, mockRequest } = getMockRequest();
|
const mockResources = getMockResources();
|
||||||
|
|
||||||
// mock includeFrozen to return true
|
// mock includeFrozen to return true
|
||||||
mockContext.core.uiSettings.client.get.mockResolvedValue(true);
|
mockResources.context.core.uiSettings.client.get.mockResolvedValue(true);
|
||||||
|
|
||||||
const { apmEventClient } = await setupRequest(mockContext, mockRequest);
|
const { apmEventClient } = await setupRequest(mockResources);
|
||||||
|
|
||||||
await apmEventClient.search({
|
await apmEventClient.search({
|
||||||
apm: { events: [] },
|
apm: { events: [] },
|
||||||
});
|
});
|
||||||
|
|
||||||
const params =
|
const params =
|
||||||
mockContext.core.elasticsearch.client.asCurrentUser.search.mock
|
mockResources.context.core.elasticsearch.client.asCurrentUser.search.mock
|
||||||
.calls[0][0];
|
.calls[0][0];
|
||||||
expect(params.ignore_throttled).toBe(false);
|
expect(params.ignore_throttled).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { APMConfig } from '../..';
|
||||||
import { KibanaRequest } from '../../../../../../src/core/server';
|
import { KibanaRequest } from '../../../../../../src/core/server';
|
||||||
import { UI_SETTINGS } from '../../../../../../src/plugins/data/common';
|
import { UI_SETTINGS } from '../../../../../../src/plugins/data/common';
|
||||||
import { UIFilters } from '../../../typings/ui_filters';
|
import { UIFilters } from '../../../typings/ui_filters';
|
||||||
import { APMRequestHandlerContext } from '../../routes/typings';
|
import { APMRouteHandlerResources } from '../../routes/typings';
|
||||||
import {
|
import {
|
||||||
ApmIndicesConfig,
|
ApmIndicesConfig,
|
||||||
getApmIndices,
|
getApmIndices,
|
||||||
|
@ -44,7 +44,7 @@ export interface SetupTimeRange {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SetupRequestParams {
|
interface SetupRequestParams {
|
||||||
query?: {
|
query: {
|
||||||
_inspect?: boolean;
|
_inspect?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -64,13 +64,19 @@ type InferSetup<TParams extends SetupRequestParams> = Setup &
|
||||||
(TParams extends { query: { start: number } } ? { start: number } : {}) &
|
(TParams extends { query: { start: number } } ? { start: number } : {}) &
|
||||||
(TParams extends { query: { end: number } } ? { end: number } : {});
|
(TParams extends { query: { end: number } } ? { end: number } : {});
|
||||||
|
|
||||||
export async function setupRequest<TParams extends SetupRequestParams>(
|
export async function setupRequest<TParams extends SetupRequestParams>({
|
||||||
context: APMRequestHandlerContext<TParams>,
|
context,
|
||||||
request: KibanaRequest
|
params,
|
||||||
): Promise<InferSetup<TParams>> {
|
core,
|
||||||
|
plugins,
|
||||||
|
request,
|
||||||
|
config,
|
||||||
|
logger,
|
||||||
|
}: APMRouteHandlerResources & {
|
||||||
|
params: TParams;
|
||||||
|
}): Promise<InferSetup<TParams>> {
|
||||||
return withApmSpan('setup_request', async () => {
|
return withApmSpan('setup_request', async () => {
|
||||||
const { config, logger } = context;
|
const { query } = params;
|
||||||
const { query } = context.params;
|
|
||||||
|
|
||||||
const [indices, includeFrozen] = await Promise.all([
|
const [indices, includeFrozen] = await Promise.all([
|
||||||
getApmIndices({
|
getApmIndices({
|
||||||
|
@ -88,7 +94,7 @@ export async function setupRequest<TParams extends SetupRequestParams>(
|
||||||
indices,
|
indices,
|
||||||
apmEventClient: createApmEventClient({
|
apmEventClient: createApmEventClient({
|
||||||
esClient: context.core.elasticsearch.client.asCurrentUser,
|
esClient: context.core.elasticsearch.client.asCurrentUser,
|
||||||
debug: context.params.query._inspect,
|
debug: query._inspect,
|
||||||
request,
|
request,
|
||||||
indices,
|
indices,
|
||||||
options: { includeFrozen },
|
options: { includeFrozen },
|
||||||
|
@ -96,11 +102,12 @@ export async function setupRequest<TParams extends SetupRequestParams>(
|
||||||
internalClient: createInternalESClient({
|
internalClient: createInternalESClient({
|
||||||
context,
|
context,
|
||||||
request,
|
request,
|
||||||
|
debug: query._inspect,
|
||||||
}),
|
}),
|
||||||
ml:
|
ml:
|
||||||
context.plugins.ml && isActivePlatinumLicense(context.licensing.license)
|
plugins.ml && isActivePlatinumLicense(context.licensing.license)
|
||||||
? getMlSetup(
|
? getMlSetup(
|
||||||
context.plugins.ml,
|
plugins.ml.setup,
|
||||||
context.core.savedObjects.client,
|
context.core.savedObjects.client,
|
||||||
request
|
request
|
||||||
)
|
)
|
||||||
|
@ -118,8 +125,8 @@ export async function setupRequest<TParams extends SetupRequestParams>(
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMlSetup(
|
function getMlSetup(
|
||||||
ml: Required<APMRequestHandlerContext['plugins']>['ml'],
|
ml: Required<APMRouteHandlerResources['plugins']>['ml']['setup'],
|
||||||
savedObjectsClient: APMRequestHandlerContext['core']['savedObjects']['client'],
|
savedObjectsClient: APMRouteHandlerResources['context']['core']['savedObjects']['client'],
|
||||||
request: KibanaRequest
|
request: KibanaRequest
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -8,21 +8,9 @@
|
||||||
import { createStaticIndexPattern } from './create_static_index_pattern';
|
import { createStaticIndexPattern } from './create_static_index_pattern';
|
||||||
import { Setup } from '../helpers/setup_request';
|
import { Setup } from '../helpers/setup_request';
|
||||||
import * as HistoricalAgentData from '../services/get_services/has_historical_agent_data';
|
import * as HistoricalAgentData from '../services/get_services/has_historical_agent_data';
|
||||||
import { APMRequestHandlerContext } from '../../routes/typings';
|
|
||||||
import { InternalSavedObjectsClient } from '../helpers/get_internal_saved_objects_client';
|
import { InternalSavedObjectsClient } from '../helpers/get_internal_saved_objects_client';
|
||||||
|
import { APMConfig } from '../..';
|
||||||
|
|
||||||
function getMockContext(config: Record<string, unknown>) {
|
|
||||||
return ({
|
|
||||||
config,
|
|
||||||
core: {
|
|
||||||
savedObjects: {
|
|
||||||
client: {
|
|
||||||
create: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as unknown) as APMRequestHandlerContext;
|
|
||||||
}
|
|
||||||
function getMockSavedObjectsClient() {
|
function getMockSavedObjectsClient() {
|
||||||
return ({
|
return ({
|
||||||
create: jest.fn(),
|
create: jest.fn(),
|
||||||
|
@ -32,13 +20,13 @@ function getMockSavedObjectsClient() {
|
||||||
describe('createStaticIndexPattern', () => {
|
describe('createStaticIndexPattern', () => {
|
||||||
it(`should not create index pattern if 'xpack.apm.autocreateApmIndexPattern=false'`, async () => {
|
it(`should not create index pattern if 'xpack.apm.autocreateApmIndexPattern=false'`, async () => {
|
||||||
const setup = {} as Setup;
|
const setup = {} as Setup;
|
||||||
const context = getMockContext({
|
|
||||||
'xpack.apm.autocreateApmIndexPattern': false,
|
|
||||||
});
|
|
||||||
const savedObjectsClient = getMockSavedObjectsClient();
|
const savedObjectsClient = getMockSavedObjectsClient();
|
||||||
await createStaticIndexPattern(
|
await createStaticIndexPattern(
|
||||||
setup,
|
setup,
|
||||||
context,
|
{
|
||||||
|
'xpack.apm.autocreateApmIndexPattern': false,
|
||||||
|
} as APMConfig,
|
||||||
savedObjectsClient,
|
savedObjectsClient,
|
||||||
'default'
|
'default'
|
||||||
);
|
);
|
||||||
|
@ -47,9 +35,6 @@ describe('createStaticIndexPattern', () => {
|
||||||
|
|
||||||
it(`should not create index pattern if no APM data is found`, async () => {
|
it(`should not create index pattern if no APM data is found`, async () => {
|
||||||
const setup = {} as Setup;
|
const setup = {} as Setup;
|
||||||
const context = getMockContext({
|
|
||||||
'xpack.apm.autocreateApmIndexPattern': true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// does not have APM data
|
// does not have APM data
|
||||||
jest
|
jest
|
||||||
|
@ -60,7 +45,9 @@ describe('createStaticIndexPattern', () => {
|
||||||
|
|
||||||
await createStaticIndexPattern(
|
await createStaticIndexPattern(
|
||||||
setup,
|
setup,
|
||||||
context,
|
{
|
||||||
|
'xpack.apm.autocreateApmIndexPattern': true,
|
||||||
|
} as APMConfig,
|
||||||
savedObjectsClient,
|
savedObjectsClient,
|
||||||
'default'
|
'default'
|
||||||
);
|
);
|
||||||
|
@ -69,9 +56,6 @@ describe('createStaticIndexPattern', () => {
|
||||||
|
|
||||||
it(`should create index pattern`, async () => {
|
it(`should create index pattern`, async () => {
|
||||||
const setup = {} as Setup;
|
const setup = {} as Setup;
|
||||||
const context = getMockContext({
|
|
||||||
'xpack.apm.autocreateApmIndexPattern': true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// does have APM data
|
// does have APM data
|
||||||
jest
|
jest
|
||||||
|
@ -82,7 +66,9 @@ describe('createStaticIndexPattern', () => {
|
||||||
|
|
||||||
await createStaticIndexPattern(
|
await createStaticIndexPattern(
|
||||||
setup,
|
setup,
|
||||||
context,
|
{
|
||||||
|
'xpack.apm.autocreateApmIndexPattern': true,
|
||||||
|
} as APMConfig,
|
||||||
savedObjectsClient,
|
savedObjectsClient,
|
||||||
'default'
|
'default'
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,20 +12,18 @@ import {
|
||||||
} from '../../../../../../src/plugins/apm_oss/server';
|
} from '../../../../../../src/plugins/apm_oss/server';
|
||||||
import { hasHistoricalAgentData } from '../services/get_services/has_historical_agent_data';
|
import { hasHistoricalAgentData } from '../services/get_services/has_historical_agent_data';
|
||||||
import { Setup } from '../helpers/setup_request';
|
import { Setup } from '../helpers/setup_request';
|
||||||
import { APMRequestHandlerContext } from '../../routes/typings';
|
import { APMRouteHandlerResources } from '../../routes/typings';
|
||||||
import { InternalSavedObjectsClient } from '../helpers/get_internal_saved_objects_client.js';
|
import { InternalSavedObjectsClient } from '../helpers/get_internal_saved_objects_client.js';
|
||||||
import { withApmSpan } from '../../utils/with_apm_span';
|
import { withApmSpan } from '../../utils/with_apm_span';
|
||||||
import { getApmIndexPatternTitle } from './get_apm_index_pattern_title';
|
import { getApmIndexPatternTitle } from './get_apm_index_pattern_title';
|
||||||
|
|
||||||
export async function createStaticIndexPattern(
|
export async function createStaticIndexPattern(
|
||||||
setup: Setup,
|
setup: Setup,
|
||||||
context: APMRequestHandlerContext,
|
config: APMRouteHandlerResources['config'],
|
||||||
savedObjectsClient: InternalSavedObjectsClient,
|
savedObjectsClient: InternalSavedObjectsClient,
|
||||||
spaceId: string | undefined
|
spaceId: string | undefined
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
return withApmSpan('create_static_index_pattern', async () => {
|
return withApmSpan('create_static_index_pattern', async () => {
|
||||||
const { config } = context;
|
|
||||||
|
|
||||||
// don't autocreate APM index pattern if it's been disabled via the config
|
// don't autocreate APM index pattern if it's been disabled via the config
|
||||||
if (!config['xpack.apm.autocreateApmIndexPattern']) {
|
if (!config['xpack.apm.autocreateApmIndexPattern']) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -39,7 +37,7 @@ export async function createStaticIndexPattern(
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const apmIndexPatternTitle = getApmIndexPatternTitle(context);
|
const apmIndexPatternTitle = getApmIndexPatternTitle(config);
|
||||||
await withApmSpan('create_index_pattern_saved_object', () =>
|
await withApmSpan('create_index_pattern_saved_object', () =>
|
||||||
savedObjectsClient.create(
|
savedObjectsClient.create(
|
||||||
'index-pattern',
|
'index-pattern',
|
||||||
|
|
|
@ -5,8 +5,10 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { APMRequestHandlerContext } from '../../routes/typings';
|
import { APMRouteHandlerResources } from '../../routes/typings';
|
||||||
|
|
||||||
export function getApmIndexPatternTitle(context: APMRequestHandlerContext) {
|
export function getApmIndexPatternTitle(
|
||||||
return context.config['apm_oss.indexPattern'];
|
config: APMRouteHandlerResources['config']
|
||||||
|
) {
|
||||||
|
return config['apm_oss.indexPattern'];
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
IndexPatternsFetcher,
|
IndexPatternsFetcher,
|
||||||
FieldDescriptor,
|
FieldDescriptor,
|
||||||
} from '../../../../../../src/plugins/data/server';
|
} from '../../../../../../src/plugins/data/server';
|
||||||
import { APMRequestHandlerContext } from '../../routes/typings';
|
import { APMRouteHandlerResources } from '../../routes/typings';
|
||||||
import { withApmSpan } from '../../utils/with_apm_span';
|
import { withApmSpan } from '../../utils/with_apm_span';
|
||||||
|
|
||||||
export interface IndexPatternTitleAndFields {
|
export interface IndexPatternTitleAndFields {
|
||||||
|
@ -20,12 +20,12 @@ export interface IndexPatternTitleAndFields {
|
||||||
|
|
||||||
// TODO: this is currently cached globally. In the future we might want to cache this per user
|
// TODO: this is currently cached globally. In the future we might want to cache this per user
|
||||||
export const getDynamicIndexPattern = ({
|
export const getDynamicIndexPattern = ({
|
||||||
|
config,
|
||||||
context,
|
context,
|
||||||
}: {
|
logger,
|
||||||
context: APMRequestHandlerContext;
|
}: Pick<APMRouteHandlerResources, 'logger' | 'config' | 'context'>) => {
|
||||||
}) => {
|
|
||||||
return withApmSpan('get_dynamic_index_pattern', async () => {
|
return withApmSpan('get_dynamic_index_pattern', async () => {
|
||||||
const indexPatternTitle = context.config['apm_oss.indexPattern'];
|
const indexPatternTitle = config['apm_oss.indexPattern'];
|
||||||
|
|
||||||
const indexPatternsFetcher = new IndexPatternsFetcher(
|
const indexPatternsFetcher = new IndexPatternsFetcher(
|
||||||
context.core.elasticsearch.client.asCurrentUser
|
context.core.elasticsearch.client.asCurrentUser
|
||||||
|
@ -50,7 +50,7 @@ export const getDynamicIndexPattern = ({
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const notExists = e.output?.statusCode === 404;
|
const notExists = e.output?.statusCode === 404;
|
||||||
if (notExists) {
|
if (notExists) {
|
||||||
context.logger.error(
|
logger.error(
|
||||||
`Could not get dynamic index pattern because indices "${indexPatternTitle}" don't exist`
|
`Could not get dynamic index pattern because indices "${indexPatternTitle}" don't exist`
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {
|
||||||
APM_INDICES_SAVED_OBJECT_ID,
|
APM_INDICES_SAVED_OBJECT_ID,
|
||||||
} from '../../../../common/apm_saved_object_constants';
|
} from '../../../../common/apm_saved_object_constants';
|
||||||
import { APMConfig } from '../../..';
|
import { APMConfig } from '../../..';
|
||||||
import { APMRequestHandlerContext } from '../../../routes/typings';
|
import { APMRouteHandlerResources } from '../../../routes/typings';
|
||||||
import { withApmSpan } from '../../../utils/with_apm_span';
|
import { withApmSpan } from '../../../utils/with_apm_span';
|
||||||
|
|
||||||
type ISavedObjectsClient = Pick<SavedObjectsClient, 'get'>;
|
type ISavedObjectsClient = Pick<SavedObjectsClient, 'get'>;
|
||||||
|
@ -91,9 +91,8 @@ const APM_UI_INDICES: ApmIndicesName[] = [
|
||||||
|
|
||||||
export async function getApmIndexSettings({
|
export async function getApmIndexSettings({
|
||||||
context,
|
context,
|
||||||
}: {
|
config,
|
||||||
context: APMRequestHandlerContext;
|
}: Pick<APMRouteHandlerResources, 'context' | 'config'>) {
|
||||||
}) {
|
|
||||||
let apmIndicesSavedObject: PromiseReturnType<typeof getApmIndicesSavedObject>;
|
let apmIndicesSavedObject: PromiseReturnType<typeof getApmIndicesSavedObject>;
|
||||||
try {
|
try {
|
||||||
apmIndicesSavedObject = await getApmIndicesSavedObject(
|
apmIndicesSavedObject = await getApmIndicesSavedObject(
|
||||||
|
@ -106,7 +105,7 @@ export async function getApmIndexSettings({
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const apmIndicesConfig = getApmIndicesConfig(context.config);
|
const apmIndicesConfig = getApmIndicesConfig(config);
|
||||||
|
|
||||||
return APM_UI_INDICES.map((configurationName) => ({
|
return APM_UI_INDICES.map((configurationName) => ({
|
||||||
configurationName,
|
configurationName,
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { combineLatest, Observable } from 'rxjs';
|
import { combineLatest } from 'rxjs';
|
||||||
import { map, take } from 'rxjs/operators';
|
import { map, take } from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
CoreSetup,
|
CoreSetup,
|
||||||
|
@ -16,22 +16,10 @@ import {
|
||||||
Plugin,
|
Plugin,
|
||||||
PluginInitializerContext,
|
PluginInitializerContext,
|
||||||
} from 'src/core/server';
|
} from 'src/core/server';
|
||||||
import { SpacesPluginSetup } from '../../spaces/server';
|
import { mapValues } from 'lodash';
|
||||||
import { APMConfig, APMXPackConfig } from '.';
|
import { APMConfig, APMXPackConfig } from '.';
|
||||||
import { mergeConfigs } from './index';
|
import { mergeConfigs } from './index';
|
||||||
import { APMOSSPluginSetup } from '../../../../src/plugins/apm_oss/server';
|
|
||||||
import { HomeServerPluginSetup } from '../../../../src/plugins/home/server';
|
|
||||||
import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server';
|
|
||||||
import { UI_SETTINGS } from '../../../../src/plugins/data/common';
|
import { UI_SETTINGS } from '../../../../src/plugins/data/common';
|
||||||
import { ActionsPlugin } from '../../actions/server';
|
|
||||||
import { AlertingPlugin } from '../../alerting/server';
|
|
||||||
import { CloudSetup } from '../../cloud/server';
|
|
||||||
import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server';
|
|
||||||
import { LicensingPluginSetup } from '../../licensing/server';
|
|
||||||
import { MlPluginSetup } from '../../ml/server';
|
|
||||||
import { ObservabilityPluginSetup } from '../../observability/server';
|
|
||||||
import { SecurityPluginSetup } from '../../security/server';
|
|
||||||
import { TaskManagerSetupContract } from '../../task_manager/server';
|
|
||||||
import { APM_FEATURE, registerFeaturesUsage } from './feature';
|
import { APM_FEATURE, registerFeaturesUsage } from './feature';
|
||||||
import { registerApmAlerts } from './lib/alerts/register_apm_alerts';
|
import { registerApmAlerts } from './lib/alerts/register_apm_alerts';
|
||||||
import { createApmTelemetry } from './lib/apm_telemetry';
|
import { createApmTelemetry } from './lib/apm_telemetry';
|
||||||
|
@ -40,23 +28,29 @@ import { getInternalSavedObjectsClient } from './lib/helpers/get_internal_saved_
|
||||||
import { createApmAgentConfigurationIndex } from './lib/settings/agent_configuration/create_agent_config_index';
|
import { createApmAgentConfigurationIndex } from './lib/settings/agent_configuration/create_agent_config_index';
|
||||||
import { getApmIndices } from './lib/settings/apm_indices/get_apm_indices';
|
import { getApmIndices } from './lib/settings/apm_indices/get_apm_indices';
|
||||||
import { createApmCustomLinkIndex } from './lib/settings/custom_link/create_custom_link_index';
|
import { createApmCustomLinkIndex } from './lib/settings/custom_link/create_custom_link_index';
|
||||||
import { createApmApi } from './routes/create_apm_api';
|
|
||||||
import { apmIndices, apmTelemetry } from './saved_objects';
|
import { apmIndices, apmTelemetry } from './saved_objects';
|
||||||
import { createElasticCloudInstructions } from './tutorial/elastic_cloud';
|
import { createElasticCloudInstructions } from './tutorial/elastic_cloud';
|
||||||
import { uiSettings } from './ui_settings';
|
import { uiSettings } from './ui_settings';
|
||||||
import type { ApmPluginRequestHandlerContext } from './routes/typings';
|
import type {
|
||||||
|
ApmPluginRequestHandlerContext,
|
||||||
|
APMRouteHandlerResources,
|
||||||
|
} from './routes/typings';
|
||||||
|
import {
|
||||||
|
APMPluginSetup,
|
||||||
|
APMPluginSetupDependencies,
|
||||||
|
APMPluginStartDependencies,
|
||||||
|
} from './types';
|
||||||
|
import { registerRoutes } from './routes/register_routes';
|
||||||
|
import { getGlobalApmServerRouteRepository } from './routes/get_global_apm_server_route_repository';
|
||||||
|
|
||||||
export interface APMPluginSetup {
|
export class APMPlugin
|
||||||
config$: Observable<APMConfig>;
|
implements
|
||||||
getApmIndices: () => ReturnType<typeof getApmIndices>;
|
Plugin<
|
||||||
createApmEventClient: (params: {
|
APMPluginSetup,
|
||||||
debug?: boolean;
|
void,
|
||||||
request: KibanaRequest;
|
APMPluginSetupDependencies,
|
||||||
context: ApmPluginRequestHandlerContext;
|
APMPluginStartDependencies
|
||||||
}) => Promise<ReturnType<typeof createApmEventClient>>;
|
> {
|
||||||
}
|
|
||||||
|
|
||||||
export class APMPlugin implements Plugin<APMPluginSetup> {
|
|
||||||
private currentConfig?: APMConfig;
|
private currentConfig?: APMConfig;
|
||||||
private logger?: Logger;
|
private logger?: Logger;
|
||||||
constructor(private readonly initContext: PluginInitializerContext) {
|
constructor(private readonly initContext: PluginInitializerContext) {
|
||||||
|
@ -64,22 +58,8 @@ export class APMPlugin implements Plugin<APMPluginSetup> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public setup(
|
public setup(
|
||||||
core: CoreSetup,
|
core: CoreSetup<APMPluginStartDependencies>,
|
||||||
plugins: {
|
plugins: Omit<APMPluginSetupDependencies, 'core'>
|
||||||
spaces?: SpacesPluginSetup;
|
|
||||||
apmOss: APMOSSPluginSetup;
|
|
||||||
home: HomeServerPluginSetup;
|
|
||||||
licensing: LicensingPluginSetup;
|
|
||||||
cloud?: CloudSetup;
|
|
||||||
usageCollection?: UsageCollectionSetup;
|
|
||||||
taskManager?: TaskManagerSetupContract;
|
|
||||||
alerting?: AlertingPlugin['setup'];
|
|
||||||
actions?: ActionsPlugin['setup'];
|
|
||||||
observability?: ObservabilityPluginSetup;
|
|
||||||
features: FeaturesPluginSetup;
|
|
||||||
security?: SecurityPluginSetup;
|
|
||||||
ml?: MlPluginSetup;
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
this.logger = this.initContext.logger.get();
|
this.logger = this.initContext.logger.get();
|
||||||
const config$ = this.initContext.config.create<APMXPackConfig>();
|
const config$ = this.initContext.config.create<APMXPackConfig>();
|
||||||
|
@ -101,11 +81,13 @@ export class APMPlugin implements Plugin<APMPluginSetup> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentConfig = mergeConfigs(
|
const currentConfig = mergeConfigs(
|
||||||
plugins.apmOss.config,
|
plugins.apmOss.config,
|
||||||
this.initContext.config.get<APMXPackConfig>()
|
this.initContext.config.get<APMXPackConfig>()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.currentConfig = currentConfig;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
plugins.taskManager &&
|
plugins.taskManager &&
|
||||||
plugins.usageCollection &&
|
plugins.usageCollection &&
|
||||||
|
@ -122,8 +104,8 @@ export class APMPlugin implements Plugin<APMPluginSetup> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ossTutorialProvider = plugins.apmOss.getRegisteredTutorialProvider();
|
const ossTutorialProvider = plugins.apmOss.getRegisteredTutorialProvider();
|
||||||
plugins.home.tutorials.unregisterTutorial(ossTutorialProvider);
|
plugins.home?.tutorials.unregisterTutorial(ossTutorialProvider);
|
||||||
plugins.home.tutorials.registerTutorial(() => {
|
plugins.home?.tutorials.registerTutorial(() => {
|
||||||
const ossPart = ossTutorialProvider({});
|
const ossPart = ossTutorialProvider({});
|
||||||
if (this.currentConfig!['xpack.apm.ui.enabled'] && ossPart.artifacts) {
|
if (this.currentConfig!['xpack.apm.ui.enabled'] && ossPart.artifacts) {
|
||||||
ossPart.artifacts.application = {
|
ossPart.artifacts.application = {
|
||||||
|
@ -147,10 +129,26 @@ export class APMPlugin implements Plugin<APMPluginSetup> {
|
||||||
|
|
||||||
registerFeaturesUsage({ licensingPlugin: plugins.licensing });
|
registerFeaturesUsage({ licensingPlugin: plugins.licensing });
|
||||||
|
|
||||||
createApmApi().init(core, {
|
registerRoutes({
|
||||||
config$: mergedConfig$,
|
core: {
|
||||||
logger: this.logger!,
|
setup: core,
|
||||||
plugins,
|
start: () => core.getStartServices().then(([coreStart]) => coreStart),
|
||||||
|
},
|
||||||
|
logger: this.logger,
|
||||||
|
config: currentConfig,
|
||||||
|
repository: getGlobalApmServerRouteRepository(),
|
||||||
|
plugins: mapValues(plugins, (value, key) => {
|
||||||
|
return {
|
||||||
|
setup: value,
|
||||||
|
start: () =>
|
||||||
|
core.getStartServices().then((services) => {
|
||||||
|
const [, pluginsStartContracts] = services;
|
||||||
|
return pluginsStartContracts[
|
||||||
|
key as keyof APMPluginStartDependencies
|
||||||
|
];
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}) as APMRouteHandlerResources['plugins'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const boundGetApmIndices = async () =>
|
const boundGetApmIndices = async () =>
|
||||||
|
|
|
@ -10,7 +10,8 @@ import { getTransactionDurationChartPreview } from '../../lib/alerts/chart_previ
|
||||||
import { getTransactionErrorCountChartPreview } from '../../lib/alerts/chart_preview/get_transaction_error_count';
|
import { getTransactionErrorCountChartPreview } from '../../lib/alerts/chart_preview/get_transaction_error_count';
|
||||||
import { getTransactionErrorRateChartPreview } from '../../lib/alerts/chart_preview/get_transaction_error_rate';
|
import { getTransactionErrorRateChartPreview } from '../../lib/alerts/chart_preview/get_transaction_error_rate';
|
||||||
import { setupRequest } from '../../lib/helpers/setup_request';
|
import { setupRequest } from '../../lib/helpers/setup_request';
|
||||||
import { createRoute } from '../create_route';
|
import { createApmServerRoute } from '../create_apm_server_route';
|
||||||
|
import { createApmServerRouteRepository } from '../create_apm_server_route_repository';
|
||||||
import { rangeRt } from '../default_api_types';
|
import { rangeRt } from '../default_api_types';
|
||||||
|
|
||||||
const alertParamsRt = t.intersection([
|
const alertParamsRt = t.intersection([
|
||||||
|
@ -29,13 +30,14 @@ const alertParamsRt = t.intersection([
|
||||||
|
|
||||||
export type AlertParams = t.TypeOf<typeof alertParamsRt>;
|
export type AlertParams = t.TypeOf<typeof alertParamsRt>;
|
||||||
|
|
||||||
export const transactionErrorRateChartPreview = createRoute({
|
const transactionErrorRateChartPreview = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_rate',
|
endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_rate',
|
||||||
params: t.type({ query: alertParamsRt }),
|
params: t.type({ query: alertParamsRt }),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { _inspect, ...alertParams } = context.params.query;
|
const { params } = resources;
|
||||||
|
const { _inspect, ...alertParams } = params.query;
|
||||||
|
|
||||||
const errorRateChartPreview = await getTransactionErrorRateChartPreview({
|
const errorRateChartPreview = await getTransactionErrorRateChartPreview({
|
||||||
setup,
|
setup,
|
||||||
|
@ -46,13 +48,16 @@ export const transactionErrorRateChartPreview = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const transactionErrorCountChartPreview = createRoute({
|
const transactionErrorCountChartPreview = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_count',
|
endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_count',
|
||||||
params: t.type({ query: alertParamsRt }),
|
params: t.type({ query: alertParamsRt }),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { _inspect, ...alertParams } = context.params.query;
|
const { params } = resources;
|
||||||
|
|
||||||
|
const { _inspect, ...alertParams } = params.query;
|
||||||
|
|
||||||
const errorCountChartPreview = await getTransactionErrorCountChartPreview({
|
const errorCountChartPreview = await getTransactionErrorCountChartPreview({
|
||||||
setup,
|
setup,
|
||||||
alertParams,
|
alertParams,
|
||||||
|
@ -62,13 +67,16 @@ export const transactionErrorCountChartPreview = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const transactionDurationChartPreview = createRoute({
|
const transactionDurationChartPreview = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/alerts/chart_preview/transaction_duration',
|
endpoint: 'GET /api/apm/alerts/chart_preview/transaction_duration',
|
||||||
params: t.type({ query: alertParamsRt }),
|
params: t.type({ query: alertParamsRt }),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { _inspect, ...alertParams } = context.params.query;
|
|
||||||
|
const { params } = resources;
|
||||||
|
|
||||||
|
const { _inspect, ...alertParams } = params.query;
|
||||||
|
|
||||||
const latencyChartPreview = await getTransactionDurationChartPreview({
|
const latencyChartPreview = await getTransactionDurationChartPreview({
|
||||||
alertParams,
|
alertParams,
|
||||||
|
@ -78,3 +86,9 @@ export const transactionDurationChartPreview = createRoute({
|
||||||
return { latencyChartPreview };
|
return { latencyChartPreview };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const alertsChartPreviewRouteRepository = createApmServerRouteRepository()
|
||||||
|
.add(transactionErrorRateChartPreview)
|
||||||
|
.add(transactionDurationChartPreview)
|
||||||
|
.add(transactionErrorCountChartPreview)
|
||||||
|
.add(transactionDurationChartPreview);
|
||||||
|
|
|
@ -14,7 +14,8 @@ import { getOverallErrorTimeseries } from '../lib/correlations/errors/get_overal
|
||||||
import { getCorrelationsForSlowTransactions } from '../lib/correlations/latency/get_correlations_for_slow_transactions';
|
import { getCorrelationsForSlowTransactions } from '../lib/correlations/latency/get_correlations_for_slow_transactions';
|
||||||
import { getOverallLatencyDistribution } from '../lib/correlations/latency/get_overall_latency_distribution';
|
import { getOverallLatencyDistribution } from '../lib/correlations/latency/get_overall_latency_distribution';
|
||||||
import { setupRequest } from '../lib/helpers/setup_request';
|
import { setupRequest } from '../lib/helpers/setup_request';
|
||||||
import { createRoute } from './create_route';
|
import { createApmServerRoute } from './create_apm_server_route';
|
||||||
|
import { createApmServerRouteRepository } from './create_apm_server_route_repository';
|
||||||
import { environmentRt, kueryRt, rangeRt } from './default_api_types';
|
import { environmentRt, kueryRt, rangeRt } from './default_api_types';
|
||||||
|
|
||||||
const INVALID_LICENSE = i18n.translate(
|
const INVALID_LICENSE = i18n.translate(
|
||||||
|
@ -25,7 +26,7 @@ const INVALID_LICENSE = i18n.translate(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const correlationsLatencyDistributionRoute = createRoute({
|
const correlationsLatencyDistributionRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/correlations/latency/overall_distribution',
|
endpoint: 'GET /api/apm/correlations/latency/overall_distribution',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: t.intersection([
|
query: t.intersection([
|
||||||
|
@ -40,18 +41,19 @@ export const correlationsLatencyDistributionRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
|
const { context, params } = resources;
|
||||||
if (!isActivePlatinumLicense(context.licensing.license)) {
|
if (!isActivePlatinumLicense(context.licensing.license)) {
|
||||||
throw Boom.forbidden(INVALID_LICENSE);
|
throw Boom.forbidden(INVALID_LICENSE);
|
||||||
}
|
}
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const {
|
const {
|
||||||
environment,
|
environment,
|
||||||
kuery,
|
kuery,
|
||||||
serviceName,
|
serviceName,
|
||||||
transactionType,
|
transactionType,
|
||||||
transactionName,
|
transactionName,
|
||||||
} = context.params.query;
|
} = params.query;
|
||||||
|
|
||||||
return getOverallLatencyDistribution({
|
return getOverallLatencyDistribution({
|
||||||
environment,
|
environment,
|
||||||
|
@ -64,7 +66,7 @@ export const correlationsLatencyDistributionRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const correlationsForSlowTransactionsRoute = createRoute({
|
const correlationsForSlowTransactionsRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/correlations/latency/slow_transactions',
|
endpoint: 'GET /api/apm/correlations/latency/slow_transactions',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: t.intersection([
|
query: t.intersection([
|
||||||
|
@ -85,11 +87,13 @@ export const correlationsForSlowTransactionsRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
|
const { context, params } = resources;
|
||||||
|
|
||||||
if (!isActivePlatinumLicense(context.licensing.license)) {
|
if (!isActivePlatinumLicense(context.licensing.license)) {
|
||||||
throw Boom.forbidden(INVALID_LICENSE);
|
throw Boom.forbidden(INVALID_LICENSE);
|
||||||
}
|
}
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const {
|
const {
|
||||||
environment,
|
environment,
|
||||||
kuery,
|
kuery,
|
||||||
|
@ -100,7 +104,7 @@ export const correlationsForSlowTransactionsRoute = createRoute({
|
||||||
fieldNames,
|
fieldNames,
|
||||||
maxLatency,
|
maxLatency,
|
||||||
distributionInterval,
|
distributionInterval,
|
||||||
} = context.params.query;
|
} = params.query;
|
||||||
|
|
||||||
return getCorrelationsForSlowTransactions({
|
return getCorrelationsForSlowTransactions({
|
||||||
environment,
|
environment,
|
||||||
|
@ -117,7 +121,7 @@ export const correlationsForSlowTransactionsRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const correlationsErrorDistributionRoute = createRoute({
|
const correlationsErrorDistributionRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/correlations/errors/overall_timeseries',
|
endpoint: 'GET /api/apm/correlations/errors/overall_timeseries',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: t.intersection([
|
query: t.intersection([
|
||||||
|
@ -132,18 +136,20 @@ export const correlationsErrorDistributionRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
|
const { params, context } = resources;
|
||||||
|
|
||||||
if (!isActivePlatinumLicense(context.licensing.license)) {
|
if (!isActivePlatinumLicense(context.licensing.license)) {
|
||||||
throw Boom.forbidden(INVALID_LICENSE);
|
throw Boom.forbidden(INVALID_LICENSE);
|
||||||
}
|
}
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const {
|
const {
|
||||||
environment,
|
environment,
|
||||||
kuery,
|
kuery,
|
||||||
serviceName,
|
serviceName,
|
||||||
transactionType,
|
transactionType,
|
||||||
transactionName,
|
transactionName,
|
||||||
} = context.params.query;
|
} = params.query;
|
||||||
|
|
||||||
return getOverallErrorTimeseries({
|
return getOverallErrorTimeseries({
|
||||||
environment,
|
environment,
|
||||||
|
@ -156,7 +162,7 @@ export const correlationsErrorDistributionRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const correlationsForFailedTransactionsRoute = createRoute({
|
const correlationsForFailedTransactionsRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/correlations/errors/failed_transactions',
|
endpoint: 'GET /api/apm/correlations/errors/failed_transactions',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: t.intersection([
|
query: t.intersection([
|
||||||
|
@ -174,11 +180,12 @@ export const correlationsForFailedTransactionsRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
|
const { context, params } = resources;
|
||||||
if (!isActivePlatinumLicense(context.licensing.license)) {
|
if (!isActivePlatinumLicense(context.licensing.license)) {
|
||||||
throw Boom.forbidden(INVALID_LICENSE);
|
throw Boom.forbidden(INVALID_LICENSE);
|
||||||
}
|
}
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const {
|
const {
|
||||||
environment,
|
environment,
|
||||||
kuery,
|
kuery,
|
||||||
|
@ -186,7 +193,7 @@ export const correlationsForFailedTransactionsRoute = createRoute({
|
||||||
transactionType,
|
transactionType,
|
||||||
transactionName,
|
transactionName,
|
||||||
fieldNames,
|
fieldNames,
|
||||||
} = context.params.query;
|
} = params.query;
|
||||||
|
|
||||||
return getCorrelationsForFailedTransactions({
|
return getCorrelationsForFailedTransactions({
|
||||||
environment,
|
environment,
|
||||||
|
@ -199,3 +206,9 @@ export const correlationsForFailedTransactionsRoute = createRoute({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const correlationsRouteRepository = createApmServerRouteRepository()
|
||||||
|
.add(correlationsLatencyDistributionRoute)
|
||||||
|
.add(correlationsForSlowTransactionsRoute)
|
||||||
|
.add(correlationsErrorDistributionRoute)
|
||||||
|
.add(correlationsForFailedTransactionsRoute);
|
||||||
|
|
|
@ -1,368 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 * as t from 'io-ts';
|
|
||||||
import { createApi } from './index';
|
|
||||||
import { CoreSetup, Logger } from 'src/core/server';
|
|
||||||
import { RouteParamsRT } from '../typings';
|
|
||||||
import { BehaviorSubject } from 'rxjs';
|
|
||||||
import { APMConfig } from '../..';
|
|
||||||
import { jsonRt } from '../../../common/runtime_types/json_rt';
|
|
||||||
|
|
||||||
const getCoreMock = () => {
|
|
||||||
const get = jest.fn();
|
|
||||||
const post = jest.fn();
|
|
||||||
const put = jest.fn();
|
|
||||||
const createRouter = jest.fn().mockReturnValue({
|
|
||||||
get,
|
|
||||||
post,
|
|
||||||
put,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mock = {} as CoreSetup;
|
|
||||||
|
|
||||||
return {
|
|
||||||
mock: {
|
|
||||||
...mock,
|
|
||||||
http: {
|
|
||||||
...mock.http,
|
|
||||||
createRouter,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
get,
|
|
||||||
post,
|
|
||||||
put,
|
|
||||||
createRouter,
|
|
||||||
context: {
|
|
||||||
measure: () => undefined,
|
|
||||||
config$: new BehaviorSubject({} as APMConfig),
|
|
||||||
logger: ({
|
|
||||||
error: jest.fn(),
|
|
||||||
} as unknown) as Logger,
|
|
||||||
plugins: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const initApi = (params?: RouteParamsRT) => {
|
|
||||||
const { mock, context, createRouter, get, post } = getCoreMock();
|
|
||||||
const handlerMock = jest.fn();
|
|
||||||
createApi()
|
|
||||||
.add(() => ({
|
|
||||||
endpoint: 'GET /foo',
|
|
||||||
params,
|
|
||||||
options: { tags: ['access:apm'] },
|
|
||||||
handler: handlerMock,
|
|
||||||
}))
|
|
||||||
.init(mock, context);
|
|
||||||
|
|
||||||
const routeHandler = get.mock.calls[0][1];
|
|
||||||
|
|
||||||
const responseMock = {
|
|
||||||
ok: jest.fn(),
|
|
||||||
custom: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const simulateRequest = (requestMock: any) => {
|
|
||||||
return routeHandler(
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
// stub default values
|
|
||||||
params: {},
|
|
||||||
query: {},
|
|
||||||
body: null,
|
|
||||||
...requestMock,
|
|
||||||
},
|
|
||||||
responseMock
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
simulateRequest,
|
|
||||||
handlerMock,
|
|
||||||
createRouter,
|
|
||||||
get,
|
|
||||||
post,
|
|
||||||
responseMock,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('createApi', () => {
|
|
||||||
it('registers a route with the server', () => {
|
|
||||||
const { mock, context, createRouter, post, get, put } = getCoreMock();
|
|
||||||
|
|
||||||
createApi()
|
|
||||||
.add(() => ({
|
|
||||||
endpoint: 'GET /foo',
|
|
||||||
options: { tags: ['access:apm'] },
|
|
||||||
handler: async () => ({}),
|
|
||||||
}))
|
|
||||||
.add(() => ({
|
|
||||||
endpoint: 'POST /bar',
|
|
||||||
params: t.type({
|
|
||||||
body: t.string,
|
|
||||||
}),
|
|
||||||
options: { tags: ['access:apm'] },
|
|
||||||
handler: async () => ({}),
|
|
||||||
}))
|
|
||||||
.add(() => ({
|
|
||||||
endpoint: 'PUT /baz',
|
|
||||||
options: {
|
|
||||||
tags: ['access:apm', 'access:apm_write'],
|
|
||||||
},
|
|
||||||
handler: async () => ({}),
|
|
||||||
}))
|
|
||||||
.add({
|
|
||||||
endpoint: 'GET /qux',
|
|
||||||
options: {
|
|
||||||
tags: ['access:apm', 'access:apm_write'],
|
|
||||||
},
|
|
||||||
handler: async () => ({}),
|
|
||||||
})
|
|
||||||
.init(mock, context);
|
|
||||||
|
|
||||||
expect(createRouter).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
expect(get).toHaveBeenCalledTimes(2);
|
|
||||||
expect(post).toHaveBeenCalledTimes(1);
|
|
||||||
expect(put).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
expect(get.mock.calls[0][0]).toEqual({
|
|
||||||
options: {
|
|
||||||
tags: ['access:apm'],
|
|
||||||
},
|
|
||||||
path: '/foo',
|
|
||||||
validate: expect.anything(),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(get.mock.calls[1][0]).toEqual({
|
|
||||||
options: {
|
|
||||||
tags: ['access:apm', 'access:apm_write'],
|
|
||||||
},
|
|
||||||
path: '/qux',
|
|
||||||
validate: expect.anything(),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(post.mock.calls[0][0]).toEqual({
|
|
||||||
options: {
|
|
||||||
tags: ['access:apm'],
|
|
||||||
},
|
|
||||||
path: '/bar',
|
|
||||||
validate: expect.anything(),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(put.mock.calls[0][0]).toEqual({
|
|
||||||
options: {
|
|
||||||
tags: ['access:apm', 'access:apm_write'],
|
|
||||||
},
|
|
||||||
path: '/baz',
|
|
||||||
validate: expect.anything(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when validating', () => {
|
|
||||||
describe('_inspect', () => {
|
|
||||||
it('allows _inspect=true', async () => {
|
|
||||||
const { simulateRequest, handlerMock, responseMock } = initApi();
|
|
||||||
await simulateRequest({ query: { _inspect: 'true' } });
|
|
||||||
|
|
||||||
const params = handlerMock.mock.calls[0][0].context.params;
|
|
||||||
expect(params).toEqual({ query: { _inspect: true } });
|
|
||||||
expect(handlerMock).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
// responds with ok
|
|
||||||
expect(responseMock.custom).not.toHaveBeenCalled();
|
|
||||||
expect(responseMock.ok).toHaveBeenCalledWith({
|
|
||||||
body: { _inspect: [] },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('rejects _inspect=1', async () => {
|
|
||||||
const { simulateRequest, responseMock } = initApi();
|
|
||||||
await simulateRequest({ query: { _inspect: 1 } });
|
|
||||||
|
|
||||||
// responds with error handler
|
|
||||||
expect(responseMock.ok).not.toHaveBeenCalled();
|
|
||||||
expect(responseMock.custom).toHaveBeenCalledWith({
|
|
||||||
body: {
|
|
||||||
attributes: { _inspect: [] },
|
|
||||||
message:
|
|
||||||
'Invalid value 1 supplied to : strict_keys/query: Partial<{| _inspect: pipe(JSON, boolean) |}>/_inspect: pipe(JSON, boolean)',
|
|
||||||
},
|
|
||||||
statusCode: 400,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('allows omitting _inspect', async () => {
|
|
||||||
const { simulateRequest, handlerMock, responseMock } = initApi();
|
|
||||||
await simulateRequest({ query: {} });
|
|
||||||
|
|
||||||
const params = handlerMock.mock.calls[0][0].context.params;
|
|
||||||
expect(params).toEqual({ query: { _inspect: false } });
|
|
||||||
expect(handlerMock).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
// responds with ok
|
|
||||||
expect(responseMock.custom).not.toHaveBeenCalled();
|
|
||||||
expect(responseMock.ok).toHaveBeenCalledWith({ body: {} });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws if unknown parameters are provided', async () => {
|
|
||||||
const { simulateRequest, responseMock } = initApi();
|
|
||||||
|
|
||||||
await simulateRequest({
|
|
||||||
query: { _inspect: true, extra: '' },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(responseMock.custom).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
await simulateRequest({
|
|
||||||
body: { foo: 'bar' },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(responseMock.custom).toHaveBeenCalledTimes(2);
|
|
||||||
|
|
||||||
await simulateRequest({
|
|
||||||
params: {
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(responseMock.custom).toHaveBeenCalledTimes(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('validates path parameters', async () => {
|
|
||||||
const { simulateRequest, handlerMock, responseMock } = initApi(
|
|
||||||
t.type({
|
|
||||||
path: t.type({
|
|
||||||
foo: t.string,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
await simulateRequest({
|
|
||||||
params: {
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(handlerMock).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
expect(responseMock.ok).toHaveBeenCalledTimes(1);
|
|
||||||
expect(responseMock.custom).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
const params = handlerMock.mock.calls[0][0].context.params;
|
|
||||||
|
|
||||||
expect(params).toEqual({
|
|
||||||
path: {
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
query: {
|
|
||||||
_inspect: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await simulateRequest({
|
|
||||||
params: {
|
|
||||||
bar: 'foo',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(responseMock.custom).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
await simulateRequest({
|
|
||||||
params: {
|
|
||||||
foo: 9,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(responseMock.custom).toHaveBeenCalledTimes(2);
|
|
||||||
|
|
||||||
await simulateRequest({
|
|
||||||
params: {
|
|
||||||
foo: 'bar',
|
|
||||||
extra: '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(responseMock.custom).toHaveBeenCalledTimes(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('validates body parameters', async () => {
|
|
||||||
const { simulateRequest, handlerMock, responseMock } = initApi(
|
|
||||||
t.type({
|
|
||||||
body: t.string,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
await simulateRequest({
|
|
||||||
body: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(responseMock.custom).not.toHaveBeenCalled();
|
|
||||||
expect(handlerMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(responseMock.ok).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
const params = handlerMock.mock.calls[0][0].context.params;
|
|
||||||
|
|
||||||
expect(params).toEqual({
|
|
||||||
body: '',
|
|
||||||
query: {
|
|
||||||
_inspect: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await simulateRequest({
|
|
||||||
body: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(responseMock.custom).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('validates query parameters', async () => {
|
|
||||||
const { simulateRequest, handlerMock, responseMock } = initApi(
|
|
||||||
t.type({
|
|
||||||
query: t.type({
|
|
||||||
bar: t.string,
|
|
||||||
filterNames: jsonRt.pipe(t.array(t.string)),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
await simulateRequest({
|
|
||||||
query: {
|
|
||||||
bar: '',
|
|
||||||
_inspect: 'true',
|
|
||||||
filterNames: JSON.stringify(['hostName', 'agentName']),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(responseMock.custom).not.toHaveBeenCalled();
|
|
||||||
expect(handlerMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(responseMock.ok).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
const params = handlerMock.mock.calls[0][0].context.params;
|
|
||||||
|
|
||||||
expect(params).toEqual({
|
|
||||||
query: {
|
|
||||||
bar: '',
|
|
||||||
_inspect: true,
|
|
||||||
filterNames: ['hostName', 'agentName'],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await simulateRequest({
|
|
||||||
query: {
|
|
||||||
bar: '',
|
|
||||||
foo: '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(responseMock.custom).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,185 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 { merge as mergeLodash, pickBy, isEmpty, isPlainObject } from 'lodash';
|
|
||||||
import Boom from '@hapi/boom';
|
|
||||||
import { schema } from '@kbn/config-schema';
|
|
||||||
import * as t from 'io-ts';
|
|
||||||
import { PathReporter } from 'io-ts/lib/PathReporter';
|
|
||||||
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 { InspectResponse, RouteParamsRT, ServerAPI } from '../typings';
|
|
||||||
import { jsonRt } from '../../../common/runtime_types/json_rt';
|
|
||||||
import type { ApmPluginRequestHandlerContext } from '../typings';
|
|
||||||
|
|
||||||
const inspectRt = t.exact(
|
|
||||||
t.partial({
|
|
||||||
query: t.exact(t.partial({ _inspect: jsonRt.pipe(t.boolean) })),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
type RouteOrRouteFactoryFn = Parameters<ServerAPI<{}>['add']>[0];
|
|
||||||
|
|
||||||
const isNotEmpty = (val: any) =>
|
|
||||||
val !== undefined && val !== null && !(isPlainObject(val) && isEmpty(val));
|
|
||||||
|
|
||||||
export const inspectableEsQueriesMap = new WeakMap<
|
|
||||||
KibanaRequest,
|
|
||||||
InspectResponse
|
|
||||||
>();
|
|
||||||
|
|
||||||
export function createApi() {
|
|
||||||
const routes: RouteOrRouteFactoryFn[] = [];
|
|
||||||
const api: ServerAPI<{}> = {
|
|
||||||
_S: {},
|
|
||||||
add(route) {
|
|
||||||
routes.push((route as unknown) as RouteOrRouteFactoryFn);
|
|
||||||
return this as any;
|
|
||||||
},
|
|
||||||
init(core, { config$, logger, plugins }) {
|
|
||||||
const router = core.http.createRouter();
|
|
||||||
|
|
||||||
let config = {} as APMConfig;
|
|
||||||
|
|
||||||
config$.subscribe((val) => {
|
|
||||||
config = val;
|
|
||||||
});
|
|
||||||
|
|
||||||
routes.forEach((routeOrFactoryFn) => {
|
|
||||||
const route =
|
|
||||||
typeof routeOrFactoryFn === 'function'
|
|
||||||
? routeOrFactoryFn(core)
|
|
||||||
: routeOrFactoryFn;
|
|
||||||
|
|
||||||
const { params, endpoint, options, handler } = route;
|
|
||||||
|
|
||||||
const [method, path] = endpoint.split(' ');
|
|
||||||
const typedRouterMethod = parseMethod(method);
|
|
||||||
|
|
||||||
// For all runtime types with props, we create an exact
|
|
||||||
// version that will strip all keys that are unvalidated.
|
|
||||||
const anyObject = schema.object({}, { unknowns: 'allow' });
|
|
||||||
|
|
||||||
(router[typedRouterMethod] as RouteRegistrar<
|
|
||||||
typeof typedRouterMethod,
|
|
||||||
ApmPluginRequestHandlerContext
|
|
||||||
>)(
|
|
||||||
{
|
|
||||||
path,
|
|
||||||
options,
|
|
||||||
validate: {
|
|
||||||
// `body` can be null, but `validate` expects non-nullable types
|
|
||||||
// if any validation is defined. Not having validation currently
|
|
||||||
// means we don't get the payload. See
|
|
||||||
// https://github.com/elastic/kibana/issues/50179
|
|
||||||
body: schema.nullable(anyObject),
|
|
||||||
params: anyObject,
|
|
||||||
query: anyObject,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
async (context, request, response) => {
|
|
||||||
if (agent.isStarted()) {
|
|
||||||
agent.addLabels({
|
|
||||||
plugin: 'apm',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// init debug queries
|
|
||||||
inspectableEsQueriesMap.set(request, []);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const validParams = validateParams(request, params);
|
|
||||||
const data = await handler({
|
|
||||||
request,
|
|
||||||
context: {
|
|
||||||
...context,
|
|
||||||
plugins,
|
|
||||||
params: validParams,
|
|
||||||
config,
|
|
||||||
logger,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const body = { ...data };
|
|
||||||
if (validParams.query._inspect) {
|
|
||||||
body._inspect = inspectableEsQueriesMap.get(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanup
|
|
||||||
inspectableEsQueriesMap.delete(request);
|
|
||||||
|
|
||||||
return response.ok({ body });
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error);
|
|
||||||
const opts = {
|
|
||||||
statusCode: 500,
|
|
||||||
body: {
|
|
||||||
message: error.message,
|
|
||||||
attributes: {
|
|
||||||
_inspect: inspectableEsQueriesMap.get(request),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (Boom.isBoom(error)) {
|
|
||||||
opts.statusCode = error.output.statusCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error instanceof RequestAbortedError) {
|
|
||||||
opts.statusCode = 499;
|
|
||||||
opts.body.message = 'Client closed request';
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.custom(opts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
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)
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,230 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 {
|
|
||||||
staticIndexPatternRoute,
|
|
||||||
dynamicIndexPatternRoute,
|
|
||||||
apmIndexPatternTitleRoute,
|
|
||||||
} from './index_pattern';
|
|
||||||
import { createApi } from './create_api';
|
|
||||||
import { environmentsRoute } from './environments';
|
|
||||||
import {
|
|
||||||
errorDistributionRoute,
|
|
||||||
errorGroupsRoute,
|
|
||||||
errorsRoute,
|
|
||||||
} from './errors';
|
|
||||||
import {
|
|
||||||
serviceAgentNameRoute,
|
|
||||||
serviceTransactionTypesRoute,
|
|
||||||
servicesRoute,
|
|
||||||
serviceNodeMetadataRoute,
|
|
||||||
serviceAnnotationsRoute,
|
|
||||||
serviceAnnotationsCreateRoute,
|
|
||||||
serviceErrorGroupsPrimaryStatisticsRoute,
|
|
||||||
serviceErrorGroupsComparisonStatisticsRoute,
|
|
||||||
serviceThroughputRoute,
|
|
||||||
serviceDependenciesRoute,
|
|
||||||
serviceMetadataDetailsRoute,
|
|
||||||
serviceMetadataIconsRoute,
|
|
||||||
serviceInstancesPrimaryStatisticsRoute,
|
|
||||||
serviceInstancesComparisonStatisticsRoute,
|
|
||||||
serviceProfilingStatisticsRoute,
|
|
||||||
serviceProfilingTimelineRoute,
|
|
||||||
} from './services';
|
|
||||||
import {
|
|
||||||
agentConfigurationRoute,
|
|
||||||
getSingleAgentConfigurationRoute,
|
|
||||||
agentConfigurationSearchRoute,
|
|
||||||
deleteAgentConfigurationRoute,
|
|
||||||
listAgentConfigurationEnvironmentsRoute,
|
|
||||||
listAgentConfigurationServicesRoute,
|
|
||||||
createOrUpdateAgentConfigurationRoute,
|
|
||||||
agentConfigurationAgentNameRoute,
|
|
||||||
} from './settings/agent_configuration';
|
|
||||||
import {
|
|
||||||
apmIndexSettingsRoute,
|
|
||||||
apmIndicesRoute,
|
|
||||||
saveApmIndicesRoute,
|
|
||||||
} from './settings/apm_indices';
|
|
||||||
import { metricsChartsRoute } from './metrics';
|
|
||||||
import { serviceNodesRoute } from './service_nodes';
|
|
||||||
import {
|
|
||||||
tracesRoute,
|
|
||||||
tracesByIdRoute,
|
|
||||||
rootTransactionByTraceIdRoute,
|
|
||||||
} from './traces';
|
|
||||||
import {
|
|
||||||
correlationsLatencyDistributionRoute,
|
|
||||||
correlationsForSlowTransactionsRoute,
|
|
||||||
correlationsErrorDistributionRoute,
|
|
||||||
correlationsForFailedTransactionsRoute,
|
|
||||||
} from './correlations';
|
|
||||||
import {
|
|
||||||
transactionChartsBreakdownRoute,
|
|
||||||
transactionChartsDistributionRoute,
|
|
||||||
transactionChartsErrorRateRoute,
|
|
||||||
transactionGroupsRoute,
|
|
||||||
transactionGroupsPrimaryStatisticsRoute,
|
|
||||||
transactionLatencyChartsRoute,
|
|
||||||
transactionThroughputChartsRoute,
|
|
||||||
transactionGroupsComparisonStatisticsRoute,
|
|
||||||
} from './transactions';
|
|
||||||
import { serviceMapRoute, serviceMapServiceNodeRoute } from './service_map';
|
|
||||||
import {
|
|
||||||
createCustomLinkRoute,
|
|
||||||
updateCustomLinkRoute,
|
|
||||||
deleteCustomLinkRoute,
|
|
||||||
listCustomLinksRoute,
|
|
||||||
customLinkTransactionRoute,
|
|
||||||
} from './settings/custom_link';
|
|
||||||
import {
|
|
||||||
observabilityOverviewHasDataRoute,
|
|
||||||
observabilityOverviewRoute,
|
|
||||||
} from './observability_overview';
|
|
||||||
import {
|
|
||||||
anomalyDetectionJobsRoute,
|
|
||||||
createAnomalyDetectionJobsRoute,
|
|
||||||
anomalyDetectionEnvironmentsRoute,
|
|
||||||
} from './settings/anomaly_detection';
|
|
||||||
import {
|
|
||||||
rumHasDataRoute,
|
|
||||||
rumClientMetricsRoute,
|
|
||||||
rumJSErrors,
|
|
||||||
rumLongTaskMetrics,
|
|
||||||
rumOverviewLocalFiltersRoute,
|
|
||||||
rumPageLoadDistBreakdownRoute,
|
|
||||||
rumPageLoadDistributionRoute,
|
|
||||||
rumPageViewsTrendRoute,
|
|
||||||
rumServicesRoute,
|
|
||||||
rumUrlSearch,
|
|
||||||
rumVisitorsBreakdownRoute,
|
|
||||||
rumWebCoreVitals,
|
|
||||||
} from './rum_client';
|
|
||||||
import {
|
|
||||||
transactionErrorRateChartPreview,
|
|
||||||
transactionErrorCountChartPreview,
|
|
||||||
transactionDurationChartPreview,
|
|
||||||
} from './alerts/chart_preview';
|
|
||||||
|
|
||||||
const createApmApi = () => {
|
|
||||||
const api = createApi()
|
|
||||||
// index pattern
|
|
||||||
.add(staticIndexPatternRoute)
|
|
||||||
.add(dynamicIndexPatternRoute)
|
|
||||||
.add(apmIndexPatternTitleRoute)
|
|
||||||
|
|
||||||
// Environments
|
|
||||||
.add(environmentsRoute)
|
|
||||||
|
|
||||||
// Errors
|
|
||||||
.add(errorDistributionRoute)
|
|
||||||
.add(errorGroupsRoute)
|
|
||||||
.add(errorsRoute)
|
|
||||||
|
|
||||||
// Services
|
|
||||||
.add(serviceAgentNameRoute)
|
|
||||||
.add(serviceTransactionTypesRoute)
|
|
||||||
.add(servicesRoute)
|
|
||||||
.add(serviceNodeMetadataRoute)
|
|
||||||
.add(serviceAnnotationsRoute)
|
|
||||||
.add(serviceAnnotationsCreateRoute)
|
|
||||||
.add(serviceErrorGroupsPrimaryStatisticsRoute)
|
|
||||||
.add(serviceThroughputRoute)
|
|
||||||
.add(serviceDependenciesRoute)
|
|
||||||
.add(serviceMetadataDetailsRoute)
|
|
||||||
.add(serviceMetadataIconsRoute)
|
|
||||||
.add(serviceInstancesPrimaryStatisticsRoute)
|
|
||||||
.add(serviceInstancesComparisonStatisticsRoute)
|
|
||||||
.add(serviceErrorGroupsComparisonStatisticsRoute)
|
|
||||||
.add(serviceProfilingTimelineRoute)
|
|
||||||
.add(serviceProfilingStatisticsRoute)
|
|
||||||
|
|
||||||
// Agent configuration
|
|
||||||
.add(getSingleAgentConfigurationRoute)
|
|
||||||
.add(agentConfigurationAgentNameRoute)
|
|
||||||
.add(agentConfigurationRoute)
|
|
||||||
.add(agentConfigurationSearchRoute)
|
|
||||||
.add(deleteAgentConfigurationRoute)
|
|
||||||
.add(listAgentConfigurationEnvironmentsRoute)
|
|
||||||
.add(listAgentConfigurationServicesRoute)
|
|
||||||
.add(createOrUpdateAgentConfigurationRoute)
|
|
||||||
|
|
||||||
// Correlations
|
|
||||||
.add(correlationsLatencyDistributionRoute)
|
|
||||||
.add(correlationsForSlowTransactionsRoute)
|
|
||||||
.add(correlationsErrorDistributionRoute)
|
|
||||||
.add(correlationsForFailedTransactionsRoute)
|
|
||||||
|
|
||||||
// APM indices
|
|
||||||
.add(apmIndexSettingsRoute)
|
|
||||||
.add(apmIndicesRoute)
|
|
||||||
.add(saveApmIndicesRoute)
|
|
||||||
|
|
||||||
// Metrics
|
|
||||||
.add(metricsChartsRoute)
|
|
||||||
.add(serviceNodesRoute)
|
|
||||||
|
|
||||||
// Traces
|
|
||||||
.add(tracesRoute)
|
|
||||||
.add(tracesByIdRoute)
|
|
||||||
.add(rootTransactionByTraceIdRoute)
|
|
||||||
|
|
||||||
// Transactions
|
|
||||||
.add(transactionChartsBreakdownRoute)
|
|
||||||
.add(transactionChartsDistributionRoute)
|
|
||||||
.add(transactionChartsErrorRateRoute)
|
|
||||||
.add(transactionGroupsRoute)
|
|
||||||
.add(transactionGroupsPrimaryStatisticsRoute)
|
|
||||||
.add(transactionLatencyChartsRoute)
|
|
||||||
.add(transactionThroughputChartsRoute)
|
|
||||||
.add(transactionGroupsComparisonStatisticsRoute)
|
|
||||||
|
|
||||||
// Service map
|
|
||||||
.add(serviceMapRoute)
|
|
||||||
.add(serviceMapServiceNodeRoute)
|
|
||||||
|
|
||||||
// Custom links
|
|
||||||
.add(createCustomLinkRoute)
|
|
||||||
.add(updateCustomLinkRoute)
|
|
||||||
.add(deleteCustomLinkRoute)
|
|
||||||
.add(listCustomLinksRoute)
|
|
||||||
.add(customLinkTransactionRoute)
|
|
||||||
|
|
||||||
// Observability dashboard
|
|
||||||
.add(observabilityOverviewHasDataRoute)
|
|
||||||
.add(observabilityOverviewRoute)
|
|
||||||
|
|
||||||
// Anomaly detection
|
|
||||||
.add(anomalyDetectionJobsRoute)
|
|
||||||
.add(createAnomalyDetectionJobsRoute)
|
|
||||||
.add(anomalyDetectionEnvironmentsRoute)
|
|
||||||
|
|
||||||
// User Experience app api routes
|
|
||||||
.add(rumOverviewLocalFiltersRoute)
|
|
||||||
.add(rumPageViewsTrendRoute)
|
|
||||||
.add(rumPageLoadDistributionRoute)
|
|
||||||
.add(rumPageLoadDistBreakdownRoute)
|
|
||||||
.add(rumClientMetricsRoute)
|
|
||||||
.add(rumServicesRoute)
|
|
||||||
.add(rumVisitorsBreakdownRoute)
|
|
||||||
.add(rumWebCoreVitals)
|
|
||||||
.add(rumJSErrors)
|
|
||||||
.add(rumUrlSearch)
|
|
||||||
.add(rumLongTaskMetrics)
|
|
||||||
.add(rumHasDataRoute)
|
|
||||||
|
|
||||||
// Alerting
|
|
||||||
.add(transactionErrorCountChartPreview)
|
|
||||||
.add(transactionDurationChartPreview)
|
|
||||||
.add(transactionErrorRateChartPreview);
|
|
||||||
|
|
||||||
return api;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type APMAPI = ReturnType<typeof createApmApi>;
|
|
||||||
|
|
||||||
export { createApmApi };
|
|
13
x-pack/plugins/apm/server/routes/create_apm_server_route.ts
Normal file
13
x-pack/plugins/apm/server/routes/create_apm_server_route.ts
Normal file
|
@ -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 { createServerRouteFactory } from '@kbn/server-route-repository';
|
||||||
|
import { APMRouteCreateOptions, APMRouteHandlerResources } from './typings';
|
||||||
|
|
||||||
|
export const createApmServerRoute = createServerRouteFactory<
|
||||||
|
APMRouteHandlerResources,
|
||||||
|
APMRouteCreateOptions
|
||||||
|
>();
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* 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 { createServerRouteRepository } from '@kbn/server-route-repository';
|
||||||
|
import { APMRouteCreateOptions, APMRouteHandlerResources } from './typings';
|
||||||
|
|
||||||
|
export function createApmServerRouteRepository() {
|
||||||
|
return createServerRouteRepository<
|
||||||
|
APMRouteHandlerResources,
|
||||||
|
APMRouteCreateOptions
|
||||||
|
>();
|
||||||
|
}
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 { CoreSetup } from 'src/core/server';
|
|
||||||
import { HandlerReturn, Route, RouteParamsRT } from './typings';
|
|
||||||
|
|
||||||
export function createRoute<
|
|
||||||
TEndpoint extends string,
|
|
||||||
TReturn extends HandlerReturn,
|
|
||||||
TRouteParamsRT extends RouteParamsRT | undefined = undefined
|
|
||||||
>(
|
|
||||||
route: Route<TEndpoint, TRouteParamsRT, TReturn>
|
|
||||||
): Route<TEndpoint, TRouteParamsRT, TReturn>;
|
|
||||||
|
|
||||||
export function createRoute<
|
|
||||||
TEndpoint extends string,
|
|
||||||
TReturn extends HandlerReturn,
|
|
||||||
TRouteParamsRT extends RouteParamsRT | undefined = undefined
|
|
||||||
>(
|
|
||||||
route: (core: CoreSetup) => Route<TEndpoint, TRouteParamsRT, TReturn>
|
|
||||||
): (core: CoreSetup) => Route<TEndpoint, TRouteParamsRT, TReturn>;
|
|
||||||
|
|
||||||
export function createRoute(routeOrFactoryFn: Function | object) {
|
|
||||||
return routeOrFactoryFn;
|
|
||||||
}
|
|
|
@ -9,10 +9,11 @@ import * as t from 'io-ts';
|
||||||
import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions';
|
import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions';
|
||||||
import { setupRequest } from '../lib/helpers/setup_request';
|
import { setupRequest } from '../lib/helpers/setup_request';
|
||||||
import { getEnvironments } from '../lib/environments/get_environments';
|
import { getEnvironments } from '../lib/environments/get_environments';
|
||||||
import { createRoute } from './create_route';
|
|
||||||
import { rangeRt } from './default_api_types';
|
import { rangeRt } from './default_api_types';
|
||||||
|
import { createApmServerRoute } from './create_apm_server_route';
|
||||||
|
import { createApmServerRouteRepository } from './create_apm_server_route_repository';
|
||||||
|
|
||||||
export const environmentsRoute = createRoute({
|
const environmentsRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/environments',
|
endpoint: 'GET /api/apm/environments',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: t.intersection([
|
query: t.intersection([
|
||||||
|
@ -23,9 +24,10 @@ export const environmentsRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { serviceName } = context.params.query;
|
const { params } = resources;
|
||||||
|
const { serviceName } = params.query;
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
);
|
);
|
||||||
|
@ -39,3 +41,7 @@ export const environmentsRoute = createRoute({
|
||||||
return { environments };
|
return { environments };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const environmentsRouteRepository = createApmServerRouteRepository().add(
|
||||||
|
environmentsRoute
|
||||||
|
);
|
||||||
|
|
|
@ -6,14 +6,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as t from 'io-ts';
|
import * as t from 'io-ts';
|
||||||
import { createRoute } from './create_route';
|
import { createApmServerRoute } from './create_apm_server_route';
|
||||||
import { getErrorDistribution } from '../lib/errors/distribution/get_distribution';
|
import { getErrorDistribution } from '../lib/errors/distribution/get_distribution';
|
||||||
import { getErrorGroupSample } from '../lib/errors/get_error_group_sample';
|
import { getErrorGroupSample } from '../lib/errors/get_error_group_sample';
|
||||||
import { getErrorGroups } from '../lib/errors/get_error_groups';
|
import { getErrorGroups } from '../lib/errors/get_error_groups';
|
||||||
import { setupRequest } from '../lib/helpers/setup_request';
|
import { setupRequest } from '../lib/helpers/setup_request';
|
||||||
import { environmentRt, kueryRt, rangeRt } from './default_api_types';
|
import { environmentRt, kueryRt, rangeRt } from './default_api_types';
|
||||||
|
import { createApmServerRouteRepository } from './create_apm_server_route_repository';
|
||||||
|
|
||||||
export const errorsRoute = createRoute({
|
const errorsRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/services/{serviceName}/errors',
|
endpoint: 'GET /api/apm/services/{serviceName}/errors',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({
|
path: t.type({
|
||||||
|
@ -30,9 +31,9 @@ export const errorsRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const { params } = resources;
|
||||||
const { params } = context;
|
const setup = await setupRequest(resources);
|
||||||
const { serviceName } = params.path;
|
const { serviceName } = params.path;
|
||||||
const { environment, kuery, sortField, sortDirection } = params.query;
|
const { environment, kuery, sortField, sortDirection } = params.query;
|
||||||
|
|
||||||
|
@ -49,7 +50,7 @@ export const errorsRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const errorGroupsRoute = createRoute({
|
const errorGroupsRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/services/{serviceName}/errors/{groupId}',
|
endpoint: 'GET /api/apm/services/{serviceName}/errors/{groupId}',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({
|
path: t.type({
|
||||||
|
@ -59,10 +60,11 @@ export const errorGroupsRoute = createRoute({
|
||||||
query: t.intersection([environmentRt, kueryRt, rangeRt]),
|
query: t.intersection([environmentRt, kueryRt, rangeRt]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const { params } = resources;
|
||||||
const { serviceName, groupId } = context.params.path;
|
const setup = await setupRequest(resources);
|
||||||
const { environment, kuery } = context.params.query;
|
const { serviceName, groupId } = params.path;
|
||||||
|
const { environment, kuery } = params.query;
|
||||||
|
|
||||||
return getErrorGroupSample({
|
return getErrorGroupSample({
|
||||||
environment,
|
environment,
|
||||||
|
@ -74,7 +76,7 @@ export const errorGroupsRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const errorDistributionRoute = createRoute({
|
const errorDistributionRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/services/{serviceName}/errors/distribution',
|
endpoint: 'GET /api/apm/services/{serviceName}/errors/distribution',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({
|
path: t.type({
|
||||||
|
@ -90,9 +92,9 @@ export const errorDistributionRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { params } = context;
|
const { params } = resources;
|
||||||
const { serviceName } = params.path;
|
const { serviceName } = params.path;
|
||||||
const { environment, kuery, groupId } = params.query;
|
const { environment, kuery, groupId } = params.query;
|
||||||
return getErrorDistribution({
|
return getErrorDistribution({
|
||||||
|
@ -104,3 +106,8 @@ export const errorDistributionRoute = createRoute({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const errorsRouteRepository = createApmServerRouteRepository()
|
||||||
|
.add(errorsRoute)
|
||||||
|
.add(errorGroupsRoute)
|
||||||
|
.add(errorDistributionRoute);
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* 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 type {
|
||||||
|
ServerRouteRepository,
|
||||||
|
ReturnOf,
|
||||||
|
EndpointOf,
|
||||||
|
} from '@kbn/server-route-repository';
|
||||||
|
import { PickByValue } from 'utility-types';
|
||||||
|
import { alertsChartPreviewRouteRepository } from './alerts/chart_preview';
|
||||||
|
import { correlationsRouteRepository } from './correlations';
|
||||||
|
import { createApmServerRouteRepository } from './create_apm_server_route_repository';
|
||||||
|
import { environmentsRouteRepository } from './environments';
|
||||||
|
import { errorsRouteRepository } from './errors';
|
||||||
|
import { indexPatternRouteRepository } from './index_pattern';
|
||||||
|
import { metricsRouteRepository } from './metrics';
|
||||||
|
import { observabilityOverviewRouteRepository } from './observability_overview';
|
||||||
|
import { rumRouteRepository } from './rum_client';
|
||||||
|
import { serviceRouteRepository } from './services';
|
||||||
|
import { serviceMapRouteRepository } from './service_map';
|
||||||
|
import { serviceNodeRouteRepository } from './service_nodes';
|
||||||
|
import { agentConfigurationRouteRepository } from './settings/agent_configuration';
|
||||||
|
import { anomalyDetectionRouteRepository } from './settings/anomaly_detection';
|
||||||
|
import { apmIndicesRouteRepository } from './settings/apm_indices';
|
||||||
|
import { customLinkRouteRepository } from './settings/custom_link';
|
||||||
|
import { traceRouteRepository } from './traces';
|
||||||
|
import { transactionRouteRepository } from './transactions';
|
||||||
|
import { APMRouteHandlerResources } from './typings';
|
||||||
|
|
||||||
|
const getTypedGlobalApmServerRouteRepository = () => {
|
||||||
|
const repository = createApmServerRouteRepository()
|
||||||
|
.merge(indexPatternRouteRepository)
|
||||||
|
.merge(environmentsRouteRepository)
|
||||||
|
.merge(errorsRouteRepository)
|
||||||
|
.merge(metricsRouteRepository)
|
||||||
|
.merge(observabilityOverviewRouteRepository)
|
||||||
|
.merge(rumRouteRepository)
|
||||||
|
.merge(serviceMapRouteRepository)
|
||||||
|
.merge(serviceNodeRouteRepository)
|
||||||
|
.merge(serviceRouteRepository)
|
||||||
|
.merge(traceRouteRepository)
|
||||||
|
.merge(transactionRouteRepository)
|
||||||
|
.merge(alertsChartPreviewRouteRepository)
|
||||||
|
.merge(correlationsRouteRepository)
|
||||||
|
.merge(agentConfigurationRouteRepository)
|
||||||
|
.merge(anomalyDetectionRouteRepository)
|
||||||
|
.merge(apmIndicesRouteRepository)
|
||||||
|
.merge(customLinkRouteRepository);
|
||||||
|
|
||||||
|
return repository;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGlobalApmServerRouteRepository = () => {
|
||||||
|
return getTypedGlobalApmServerRouteRepository() as ServerRouteRepository<APMRouteHandlerResources>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type APMServerRouteRepository = ReturnType<
|
||||||
|
typeof getTypedGlobalApmServerRouteRepository
|
||||||
|
>;
|
||||||
|
|
||||||
|
// Ensure no APIs return arrays (or, by proxy, the any type),
|
||||||
|
// to guarantee compatibility with _inspect.
|
||||||
|
|
||||||
|
type CompositeEndpoint = EndpointOf<APMServerRouteRepository>;
|
||||||
|
|
||||||
|
type EndpointReturnTypes = {
|
||||||
|
[Endpoint in CompositeEndpoint]: ReturnOf<APMServerRouteRepository, Endpoint>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ArrayLikeReturnTypes = PickByValue<EndpointReturnTypes, any[]>;
|
||||||
|
|
||||||
|
type ViolatingEndpoints = keyof ArrayLikeReturnTypes;
|
||||||
|
|
||||||
|
function assertType<T = never, U extends T = never>() {}
|
||||||
|
|
||||||
|
// if any endpoint has an array-like return type, the assertion below will fail
|
||||||
|
assertType<never, ViolatingEndpoints>();
|
||||||
|
|
||||||
|
export { getGlobalApmServerRouteRepository };
|
|
@ -6,49 +6,67 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createStaticIndexPattern } from '../lib/index_pattern/create_static_index_pattern';
|
import { createStaticIndexPattern } from '../lib/index_pattern/create_static_index_pattern';
|
||||||
import { createRoute } from './create_route';
|
import { createApmServerRouteRepository } from './create_apm_server_route_repository';
|
||||||
import { setupRequest } from '../lib/helpers/setup_request';
|
import { setupRequest } from '../lib/helpers/setup_request';
|
||||||
import { getInternalSavedObjectsClient } from '../lib/helpers/get_internal_saved_objects_client';
|
|
||||||
import { getApmIndexPatternTitle } from '../lib/index_pattern/get_apm_index_pattern_title';
|
import { getApmIndexPatternTitle } from '../lib/index_pattern/get_apm_index_pattern_title';
|
||||||
import { getDynamicIndexPattern } from '../lib/index_pattern/get_dynamic_index_pattern';
|
import { getDynamicIndexPattern } from '../lib/index_pattern/get_dynamic_index_pattern';
|
||||||
|
import { createApmServerRoute } from './create_apm_server_route';
|
||||||
|
|
||||||
export const staticIndexPatternRoute = createRoute((core) => ({
|
const staticIndexPatternRoute = createApmServerRoute({
|
||||||
endpoint: 'POST /api/apm/index_pattern/static',
|
endpoint: 'POST /api/apm/index_pattern/static',
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
|
const {
|
||||||
|
request,
|
||||||
|
core,
|
||||||
|
plugins: { spaces },
|
||||||
|
config,
|
||||||
|
} = resources;
|
||||||
|
|
||||||
const [setup, savedObjectsClient] = await Promise.all([
|
const [setup, savedObjectsClient] = await Promise.all([
|
||||||
setupRequest(context, request),
|
setupRequest(resources),
|
||||||
getInternalSavedObjectsClient(core),
|
core
|
||||||
|
.start()
|
||||||
|
.then((coreStart) => coreStart.savedObjects.createInternalRepository()),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const spaceId = context.plugins.spaces?.spacesService.getSpaceId(request);
|
const spaceId = spaces?.setup.spacesService.getSpaceId(request);
|
||||||
|
|
||||||
const didCreateIndexPattern = await createStaticIndexPattern(
|
const didCreateIndexPattern = await createStaticIndexPattern(
|
||||||
setup,
|
setup,
|
||||||
context,
|
config,
|
||||||
savedObjectsClient,
|
savedObjectsClient,
|
||||||
spaceId
|
spaceId
|
||||||
);
|
);
|
||||||
|
|
||||||
return { created: didCreateIndexPattern };
|
return { created: didCreateIndexPattern };
|
||||||
},
|
},
|
||||||
}));
|
});
|
||||||
|
|
||||||
export const dynamicIndexPatternRoute = createRoute({
|
const dynamicIndexPatternRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/index_pattern/dynamic',
|
endpoint: 'GET /api/apm/index_pattern/dynamic',
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context }) => {
|
handler: async ({ context, config, logger }) => {
|
||||||
const dynamicIndexPattern = await getDynamicIndexPattern({ context });
|
const dynamicIndexPattern = await getDynamicIndexPattern({
|
||||||
|
context,
|
||||||
|
config,
|
||||||
|
logger,
|
||||||
|
});
|
||||||
return { dynamicIndexPattern };
|
return { dynamicIndexPattern };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apmIndexPatternTitleRoute = createRoute({
|
const indexPatternTitleRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/index_pattern/title',
|
endpoint: 'GET /api/apm/index_pattern/title',
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context }) => {
|
handler: async ({ config }) => {
|
||||||
return {
|
return {
|
||||||
indexPatternTitle: getApmIndexPatternTitle(context),
|
indexPatternTitle: getApmIndexPatternTitle(config),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const indexPatternRouteRepository = createApmServerRouteRepository()
|
||||||
|
.add(staticIndexPatternRoute)
|
||||||
|
.add(dynamicIndexPatternRoute)
|
||||||
|
.add(indexPatternTitleRoute);
|
||||||
|
|
|
@ -8,10 +8,11 @@
|
||||||
import * as t from 'io-ts';
|
import * as t from 'io-ts';
|
||||||
import { setupRequest } from '../lib/helpers/setup_request';
|
import { setupRequest } from '../lib/helpers/setup_request';
|
||||||
import { getMetricsChartDataByAgent } from '../lib/metrics/get_metrics_chart_data_by_agent';
|
import { getMetricsChartDataByAgent } from '../lib/metrics/get_metrics_chart_data_by_agent';
|
||||||
import { createRoute } from './create_route';
|
import { createApmServerRoute } from './create_apm_server_route';
|
||||||
|
import { createApmServerRouteRepository } from './create_apm_server_route_repository';
|
||||||
import { environmentRt, kueryRt, rangeRt } from './default_api_types';
|
import { environmentRt, kueryRt, rangeRt } from './default_api_types';
|
||||||
|
|
||||||
export const metricsChartsRoute = createRoute({
|
const metricsChartsRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/services/{serviceName}/metrics/charts',
|
endpoint: 'GET /api/apm/services/{serviceName}/metrics/charts',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({
|
path: t.type({
|
||||||
|
@ -30,9 +31,9 @@ export const metricsChartsRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const { params } = resources;
|
||||||
const { params } = context;
|
const setup = await setupRequest(resources);
|
||||||
const { serviceName } = params.path;
|
const { serviceName } = params.path;
|
||||||
const { agentName, environment, kuery, serviceNodeName } = params.query;
|
const { agentName, environment, kuery, serviceNodeName } = params.query;
|
||||||
return await getMetricsChartDataByAgent({
|
return await getMetricsChartDataByAgent({
|
||||||
|
@ -45,3 +46,7 @@ export const metricsChartsRoute = createRoute({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const metricsRouteRepository = createApmServerRouteRepository().add(
|
||||||
|
metricsChartsRoute
|
||||||
|
);
|
||||||
|
|
|
@ -10,30 +10,32 @@ import { setupRequest } from '../lib/helpers/setup_request';
|
||||||
import { getServiceCount } from '../lib/observability_overview/get_service_count';
|
import { getServiceCount } from '../lib/observability_overview/get_service_count';
|
||||||
import { getTransactionsPerMinute } from '../lib/observability_overview/get_transactions_per_minute';
|
import { getTransactionsPerMinute } from '../lib/observability_overview/get_transactions_per_minute';
|
||||||
import { getHasData } from '../lib/observability_overview/has_data';
|
import { getHasData } from '../lib/observability_overview/has_data';
|
||||||
import { createRoute } from './create_route';
|
|
||||||
import { rangeRt } from './default_api_types';
|
import { rangeRt } from './default_api_types';
|
||||||
import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions';
|
import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions';
|
||||||
import { withApmSpan } from '../utils/with_apm_span';
|
import { withApmSpan } from '../utils/with_apm_span';
|
||||||
|
import { createApmServerRouteRepository } from './create_apm_server_route_repository';
|
||||||
|
import { createApmServerRoute } from './create_apm_server_route';
|
||||||
|
|
||||||
export const observabilityOverviewHasDataRoute = createRoute({
|
const observabilityOverviewHasDataRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/observability_overview/has_data',
|
endpoint: 'GET /api/apm/observability_overview/has_data',
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const res = await getHasData({ setup });
|
const res = await getHasData({ setup });
|
||||||
return { hasData: res };
|
return { hasData: res };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const observabilityOverviewRoute = createRoute({
|
const observabilityOverviewRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/observability_overview',
|
endpoint: 'GET /api/apm/observability_overview',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: t.intersection([rangeRt, t.type({ bucketSize: t.string })]),
|
query: t.intersection([rangeRt, t.type({ bucketSize: t.string })]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { bucketSize } = context.params.query;
|
const { bucketSize } = resources.params.query;
|
||||||
|
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
);
|
);
|
||||||
|
@ -54,3 +56,7 @@ export const observabilityOverviewRoute = createRoute({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const observabilityOverviewRouteRepository = createApmServerRouteRepository()
|
||||||
|
.add(observabilityOverviewRoute)
|
||||||
|
.add(observabilityOverviewHasDataRoute);
|
||||||
|
|
507
x-pack/plugins/apm/server/routes/register_routes/index.test.ts
Normal file
507
x-pack/plugins/apm/server/routes/register_routes/index.test.ts
Normal file
|
@ -0,0 +1,507 @@
|
||||||
|
/*
|
||||||
|
* 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 { jsonRt } from '@kbn/io-ts-utils';
|
||||||
|
import { createServerRouteRepository } from '@kbn/server-route-repository';
|
||||||
|
import { ServerRoute } from '@kbn/server-route-repository/target/typings';
|
||||||
|
import * as t from 'io-ts';
|
||||||
|
import { CoreSetup, Logger } from 'src/core/server';
|
||||||
|
import { APMConfig } from '../..';
|
||||||
|
import { APMRouteCreateOptions, APMRouteHandlerResources } from '../typings';
|
||||||
|
import { registerRoutes } from './index';
|
||||||
|
|
||||||
|
type RegisterRouteDependencies = Parameters<typeof registerRoutes>[0];
|
||||||
|
|
||||||
|
const getRegisterRouteDependencies = () => {
|
||||||
|
const get = jest.fn();
|
||||||
|
const post = jest.fn();
|
||||||
|
const put = jest.fn();
|
||||||
|
const createRouter = jest.fn().mockReturnValue({
|
||||||
|
get,
|
||||||
|
post,
|
||||||
|
put,
|
||||||
|
});
|
||||||
|
|
||||||
|
const coreSetup = ({
|
||||||
|
http: {
|
||||||
|
createRouter,
|
||||||
|
},
|
||||||
|
} as unknown) as CoreSetup;
|
||||||
|
|
||||||
|
const logger = ({
|
||||||
|
error: jest.fn(),
|
||||||
|
} as unknown) as Logger;
|
||||||
|
|
||||||
|
return {
|
||||||
|
mocks: {
|
||||||
|
get,
|
||||||
|
post,
|
||||||
|
put,
|
||||||
|
createRouter,
|
||||||
|
coreSetup,
|
||||||
|
logger,
|
||||||
|
},
|
||||||
|
dependencies: ({
|
||||||
|
core: {
|
||||||
|
setup: coreSetup,
|
||||||
|
},
|
||||||
|
logger,
|
||||||
|
config: {} as APMConfig,
|
||||||
|
plugins: {},
|
||||||
|
} as unknown) as RegisterRouteDependencies,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRepository = () =>
|
||||||
|
createServerRouteRepository<
|
||||||
|
APMRouteHandlerResources,
|
||||||
|
APMRouteCreateOptions
|
||||||
|
>();
|
||||||
|
|
||||||
|
const initApi = (
|
||||||
|
routes: Array<
|
||||||
|
ServerRoute<
|
||||||
|
any,
|
||||||
|
t.Any,
|
||||||
|
APMRouteHandlerResources,
|
||||||
|
any,
|
||||||
|
APMRouteCreateOptions
|
||||||
|
>
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
const { mocks, dependencies } = getRegisterRouteDependencies();
|
||||||
|
|
||||||
|
let repository = getRepository();
|
||||||
|
|
||||||
|
routes.forEach((route) => {
|
||||||
|
repository = repository.add(route);
|
||||||
|
});
|
||||||
|
|
||||||
|
registerRoutes({
|
||||||
|
...dependencies,
|
||||||
|
repository,
|
||||||
|
});
|
||||||
|
|
||||||
|
const responseMock = {
|
||||||
|
ok: jest.fn(),
|
||||||
|
custom: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const simulateRequest = (request: {
|
||||||
|
method: 'get' | 'post' | 'put';
|
||||||
|
pathname: string;
|
||||||
|
params?: Record<string, unknown>;
|
||||||
|
body?: unknown;
|
||||||
|
query?: Record<string, unknown>;
|
||||||
|
}) => {
|
||||||
|
const [, registeredRouteHandler] =
|
||||||
|
mocks[request.method].mock.calls.find((call) => {
|
||||||
|
return call[0].path === request.pathname;
|
||||||
|
}) ?? [];
|
||||||
|
|
||||||
|
const result = registeredRouteHandler(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
params: {},
|
||||||
|
query: {},
|
||||||
|
body: null,
|
||||||
|
...request,
|
||||||
|
},
|
||||||
|
responseMock
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
simulateRequest,
|
||||||
|
mocks: {
|
||||||
|
...mocks,
|
||||||
|
response: responseMock,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('createApi', () => {
|
||||||
|
it('registers a route with the server', () => {
|
||||||
|
const {
|
||||||
|
mocks: { createRouter, get, post, put },
|
||||||
|
} = initApi([
|
||||||
|
{
|
||||||
|
endpoint: 'GET /foo',
|
||||||
|
options: { tags: ['access:apm'] },
|
||||||
|
handler: async () => ({}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
endpoint: 'POST /bar',
|
||||||
|
params: t.type({
|
||||||
|
body: t.string,
|
||||||
|
}),
|
||||||
|
options: { tags: ['access:apm'] },
|
||||||
|
handler: async () => ({}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
endpoint: 'PUT /baz',
|
||||||
|
options: {
|
||||||
|
tags: ['access:apm', 'access:apm_write'],
|
||||||
|
},
|
||||||
|
handler: async () => ({}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
endpoint: 'GET /qux',
|
||||||
|
options: {
|
||||||
|
tags: ['access:apm', 'access:apm_write'],
|
||||||
|
},
|
||||||
|
handler: async () => ({}),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(createRouter).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(get).toHaveBeenCalledTimes(2);
|
||||||
|
expect(post).toHaveBeenCalledTimes(1);
|
||||||
|
expect(put).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(get.mock.calls[0][0]).toEqual({
|
||||||
|
options: {
|
||||||
|
tags: ['access:apm'],
|
||||||
|
},
|
||||||
|
path: '/foo',
|
||||||
|
validate: expect.anything(),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(get.mock.calls[1][0]).toEqual({
|
||||||
|
options: {
|
||||||
|
tags: ['access:apm', 'access:apm_write'],
|
||||||
|
},
|
||||||
|
path: '/qux',
|
||||||
|
validate: expect.anything(),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(post.mock.calls[0][0]).toEqual({
|
||||||
|
options: {
|
||||||
|
tags: ['access:apm'],
|
||||||
|
},
|
||||||
|
path: '/bar',
|
||||||
|
validate: expect.anything(),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(put.mock.calls[0][0]).toEqual({
|
||||||
|
options: {
|
||||||
|
tags: ['access:apm', 'access:apm_write'],
|
||||||
|
},
|
||||||
|
path: '/baz',
|
||||||
|
validate: expect.anything(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when validating', () => {
|
||||||
|
describe('_inspect', () => {
|
||||||
|
it('allows _inspect=true', async () => {
|
||||||
|
const handlerMock = jest.fn();
|
||||||
|
const {
|
||||||
|
simulateRequest,
|
||||||
|
mocks: { response },
|
||||||
|
} = initApi([
|
||||||
|
{
|
||||||
|
endpoint: 'GET /foo',
|
||||||
|
options: {
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
handler: handlerMock,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await simulateRequest({
|
||||||
|
method: 'get',
|
||||||
|
pathname: '/foo',
|
||||||
|
query: { _inspect: 'true' },
|
||||||
|
});
|
||||||
|
|
||||||
|
// responds with ok
|
||||||
|
expect(response.custom).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
const params = handlerMock.mock.calls[0][0].params;
|
||||||
|
expect(params).toEqual({ query: { _inspect: true } });
|
||||||
|
expect(handlerMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(response.ok).toHaveBeenCalledWith({
|
||||||
|
body: { _inspect: [] },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects _inspect=1', async () => {
|
||||||
|
const handlerMock = jest.fn();
|
||||||
|
|
||||||
|
const {
|
||||||
|
simulateRequest,
|
||||||
|
mocks: { response },
|
||||||
|
} = initApi([
|
||||||
|
{
|
||||||
|
endpoint: 'GET /foo',
|
||||||
|
options: {
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
handler: handlerMock,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
await simulateRequest({
|
||||||
|
method: 'get',
|
||||||
|
pathname: '/foo',
|
||||||
|
query: { _inspect: 1 },
|
||||||
|
});
|
||||||
|
|
||||||
|
// responds with error handler
|
||||||
|
expect(response.ok).not.toHaveBeenCalled();
|
||||||
|
expect(response.custom).toHaveBeenCalledWith({
|
||||||
|
body: {
|
||||||
|
attributes: { _inspect: [] },
|
||||||
|
message:
|
||||||
|
'Invalid value 1 supplied to : strict_keys/query: Partial<{| _inspect: pipe(JSON, boolean) |}>/_inspect: pipe(JSON, boolean)',
|
||||||
|
},
|
||||||
|
statusCode: 400,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows omitting _inspect', async () => {
|
||||||
|
const handlerMock = jest.fn();
|
||||||
|
|
||||||
|
const {
|
||||||
|
simulateRequest,
|
||||||
|
mocks: { response },
|
||||||
|
} = initApi([
|
||||||
|
{ endpoint: 'GET /foo', options: { tags: [] }, handler: handlerMock },
|
||||||
|
]);
|
||||||
|
await simulateRequest({
|
||||||
|
method: 'get',
|
||||||
|
pathname: '/foo',
|
||||||
|
query: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
// responds with ok
|
||||||
|
expect(response.custom).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
const params = handlerMock.mock.calls[0][0].params;
|
||||||
|
expect(params).toEqual({ query: { _inspect: false } });
|
||||||
|
expect(handlerMock).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(response.ok).toHaveBeenCalledWith({ body: {} });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if unknown parameters are provided', async () => {
|
||||||
|
const {
|
||||||
|
simulateRequest,
|
||||||
|
mocks: { response },
|
||||||
|
} = initApi([
|
||||||
|
{ endpoint: 'GET /foo', options: { tags: [] }, handler: jest.fn() },
|
||||||
|
]);
|
||||||
|
|
||||||
|
await simulateRequest({
|
||||||
|
method: 'get',
|
||||||
|
pathname: '/foo',
|
||||||
|
query: { _inspect: 'true', extra: '' },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.custom).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
await simulateRequest({
|
||||||
|
method: 'get',
|
||||||
|
pathname: '/foo',
|
||||||
|
body: { foo: 'bar' },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.custom).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
|
await simulateRequest({
|
||||||
|
method: 'get',
|
||||||
|
pathname: '/foo',
|
||||||
|
params: {
|
||||||
|
foo: 'bar',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.custom).toHaveBeenCalledTimes(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates path parameters', async () => {
|
||||||
|
const handlerMock = jest.fn();
|
||||||
|
const {
|
||||||
|
simulateRequest,
|
||||||
|
mocks: { response },
|
||||||
|
} = initApi([
|
||||||
|
{
|
||||||
|
endpoint: 'GET /foo',
|
||||||
|
options: { tags: [] },
|
||||||
|
params: t.type({
|
||||||
|
path: t.type({
|
||||||
|
foo: t.string,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
handler: handlerMock,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await simulateRequest({
|
||||||
|
method: 'get',
|
||||||
|
pathname: '/foo',
|
||||||
|
params: {
|
||||||
|
foo: 'bar',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(handlerMock).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(response.ok).toHaveBeenCalledTimes(1);
|
||||||
|
expect(response.custom).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
const params = handlerMock.mock.calls[0][0].params;
|
||||||
|
|
||||||
|
expect(params).toEqual({
|
||||||
|
path: {
|
||||||
|
foo: 'bar',
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
_inspect: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await simulateRequest({
|
||||||
|
method: 'get',
|
||||||
|
pathname: '/foo',
|
||||||
|
params: {
|
||||||
|
bar: 'foo',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.custom).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
await simulateRequest({
|
||||||
|
method: 'get',
|
||||||
|
pathname: '/foo',
|
||||||
|
params: {
|
||||||
|
foo: 9,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.custom).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
|
await simulateRequest({
|
||||||
|
method: 'get',
|
||||||
|
pathname: '/foo',
|
||||||
|
params: {
|
||||||
|
foo: 'bar',
|
||||||
|
extra: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.custom).toHaveBeenCalledTimes(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates body parameters', async () => {
|
||||||
|
const handlerMock = jest.fn();
|
||||||
|
const {
|
||||||
|
simulateRequest,
|
||||||
|
mocks: { response },
|
||||||
|
} = initApi([
|
||||||
|
{
|
||||||
|
endpoint: 'GET /foo',
|
||||||
|
options: {
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
params: t.type({
|
||||||
|
body: t.string,
|
||||||
|
}),
|
||||||
|
handler: handlerMock,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await simulateRequest({
|
||||||
|
method: 'get',
|
||||||
|
pathname: '/foo',
|
||||||
|
body: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.custom).not.toHaveBeenCalled();
|
||||||
|
expect(handlerMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(response.ok).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
const params = handlerMock.mock.calls[0][0].params;
|
||||||
|
|
||||||
|
expect(params).toEqual({
|
||||||
|
body: '',
|
||||||
|
query: {
|
||||||
|
_inspect: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await simulateRequest({
|
||||||
|
method: 'get',
|
||||||
|
pathname: '/foo',
|
||||||
|
body: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.custom).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates query parameters', async () => {
|
||||||
|
const handlerMock = jest.fn();
|
||||||
|
const {
|
||||||
|
simulateRequest,
|
||||||
|
mocks: { response },
|
||||||
|
} = initApi([
|
||||||
|
{
|
||||||
|
endpoint: 'GET /foo',
|
||||||
|
options: {
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
params: t.type({
|
||||||
|
query: t.type({
|
||||||
|
bar: t.string,
|
||||||
|
filterNames: jsonRt.pipe(t.array(t.string)),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
handler: handlerMock,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await simulateRequest({
|
||||||
|
method: 'get',
|
||||||
|
pathname: '/foo',
|
||||||
|
query: {
|
||||||
|
bar: '',
|
||||||
|
_inspect: 'true',
|
||||||
|
filterNames: JSON.stringify(['hostName', 'agentName']),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.custom).not.toHaveBeenCalled();
|
||||||
|
expect(handlerMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(response.ok).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
const params = handlerMock.mock.calls[0][0].params;
|
||||||
|
|
||||||
|
expect(params).toEqual({
|
||||||
|
query: {
|
||||||
|
bar: '',
|
||||||
|
_inspect: true,
|
||||||
|
filterNames: ['hostName', 'agentName'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await simulateRequest({
|
||||||
|
method: 'get',
|
||||||
|
pathname: '/foo',
|
||||||
|
query: {
|
||||||
|
bar: '',
|
||||||
|
foo: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.custom).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
143
x-pack/plugins/apm/server/routes/register_routes/index.ts
Normal file
143
x-pack/plugins/apm/server/routes/register_routes/index.ts
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* 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 Boom from '@hapi/boom';
|
||||||
|
import * as t from 'io-ts';
|
||||||
|
import { KibanaRequest, RouteRegistrar } from 'src/core/server';
|
||||||
|
import { RequestAbortedError } from '@elastic/elasticsearch/lib/errors';
|
||||||
|
import agent from 'elastic-apm-node';
|
||||||
|
import { ServerRouteRepository } from '@kbn/server-route-repository';
|
||||||
|
import { merge } from 'lodash';
|
||||||
|
import {
|
||||||
|
decodeRequestParams,
|
||||||
|
parseEndpoint,
|
||||||
|
routeValidationObject,
|
||||||
|
} from '@kbn/server-route-repository';
|
||||||
|
import { mergeRt, jsonRt } from '@kbn/io-ts-utils';
|
||||||
|
import { pickKeys } from '../../../common/utils/pick_keys';
|
||||||
|
import { APMRouteHandlerResources, InspectResponse } from '../typings';
|
||||||
|
import type { ApmPluginRequestHandlerContext } from '../typings';
|
||||||
|
|
||||||
|
const inspectRt = t.exact(
|
||||||
|
t.partial({
|
||||||
|
query: t.exact(t.partial({ _inspect: jsonRt.pipe(t.boolean) })),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export const inspectableEsQueriesMap = new WeakMap<
|
||||||
|
KibanaRequest,
|
||||||
|
InspectResponse
|
||||||
|
>();
|
||||||
|
|
||||||
|
export function registerRoutes({
|
||||||
|
core,
|
||||||
|
repository,
|
||||||
|
plugins,
|
||||||
|
logger,
|
||||||
|
config,
|
||||||
|
}: {
|
||||||
|
core: APMRouteHandlerResources['core'];
|
||||||
|
plugins: APMRouteHandlerResources['plugins'];
|
||||||
|
logger: APMRouteHandlerResources['logger'];
|
||||||
|
repository: ServerRouteRepository<APMRouteHandlerResources>;
|
||||||
|
config: APMRouteHandlerResources['config'];
|
||||||
|
}) {
|
||||||
|
const routes = repository.getRoutes();
|
||||||
|
|
||||||
|
const router = core.setup.http.createRouter();
|
||||||
|
|
||||||
|
routes.forEach((route) => {
|
||||||
|
const { params, endpoint, options, handler } = route;
|
||||||
|
|
||||||
|
const { method, pathname } = parseEndpoint(endpoint);
|
||||||
|
|
||||||
|
(router[method] as RouteRegistrar<
|
||||||
|
typeof method,
|
||||||
|
ApmPluginRequestHandlerContext
|
||||||
|
>)(
|
||||||
|
{
|
||||||
|
path: pathname,
|
||||||
|
options,
|
||||||
|
validate: routeValidationObject,
|
||||||
|
},
|
||||||
|
async (context, request, response) => {
|
||||||
|
if (agent.isStarted()) {
|
||||||
|
agent.addLabels({
|
||||||
|
plugin: 'apm',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// init debug queries
|
||||||
|
inspectableEsQueriesMap.set(request, []);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const runtimeType = params ? mergeRt(params, inspectRt) : inspectRt;
|
||||||
|
|
||||||
|
const validatedParams = decodeRequestParams(
|
||||||
|
pickKeys(request, 'params', 'body', 'query'),
|
||||||
|
runtimeType
|
||||||
|
);
|
||||||
|
|
||||||
|
const data: Record<string, any> | undefined | null = (await handler({
|
||||||
|
request,
|
||||||
|
context,
|
||||||
|
config,
|
||||||
|
logger,
|
||||||
|
core,
|
||||||
|
plugins,
|
||||||
|
params: merge(
|
||||||
|
{
|
||||||
|
query: {
|
||||||
|
_inspect: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validatedParams
|
||||||
|
),
|
||||||
|
})) as any;
|
||||||
|
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
throw new Error('Return type cannot be an array');
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = validatedParams.query?._inspect
|
||||||
|
? {
|
||||||
|
...data,
|
||||||
|
_inspect: inspectableEsQueriesMap.get(request),
|
||||||
|
}
|
||||||
|
: { ...data };
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
inspectableEsQueriesMap.delete(request);
|
||||||
|
|
||||||
|
return response.ok({ body });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
const opts = {
|
||||||
|
statusCode: 500,
|
||||||
|
body: {
|
||||||
|
message: error.message,
|
||||||
|
attributes: {
|
||||||
|
_inspect: inspectableEsQueriesMap.get(request),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Boom.isBoom(error)) {
|
||||||
|
opts.statusCode = error.output.statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof RequestAbortedError) {
|
||||||
|
opts.statusCode = 499;
|
||||||
|
opts.body.message = 'Client closed request';
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.custom(opts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as t from 'io-ts';
|
import * as t from 'io-ts';
|
||||||
import { jsonRt } from '../../common/runtime_types/json_rt';
|
import { jsonRt } from '@kbn/io-ts-utils';
|
||||||
import { LocalUIFilterName } from '../../common/ui_filter';
|
import { LocalUIFilterName } from '../../common/ui_filter';
|
||||||
import {
|
import {
|
||||||
Setup,
|
Setup,
|
||||||
|
@ -28,9 +28,10 @@ import { getLocalUIFilters } from '../lib/rum_client/ui_filters/local_ui_filters
|
||||||
import { localUIFilterNames } from '../lib/rum_client/ui_filters/local_ui_filters/config';
|
import { localUIFilterNames } from '../lib/rum_client/ui_filters/local_ui_filters/config';
|
||||||
import { getRumPageLoadTransactionsProjection } from '../projections/rum_page_load_transactions';
|
import { getRumPageLoadTransactionsProjection } from '../projections/rum_page_load_transactions';
|
||||||
import { Projection } from '../projections/typings';
|
import { Projection } from '../projections/typings';
|
||||||
import { createRoute } from './create_route';
|
import { createApmServerRoute } from './create_apm_server_route';
|
||||||
|
import { createApmServerRouteRepository } from './create_apm_server_route_repository';
|
||||||
import { rangeRt } from './default_api_types';
|
import { rangeRt } from './default_api_types';
|
||||||
import { APMRequestHandlerContext } from './typings';
|
import { APMRouteHandlerResources } from './typings';
|
||||||
|
|
||||||
export const percentileRangeRt = t.partial({
|
export const percentileRangeRt = t.partial({
|
||||||
minPercentile: t.string,
|
minPercentile: t.string,
|
||||||
|
@ -45,18 +46,18 @@ const uxQueryRt = t.intersection([
|
||||||
t.partial({ urlQuery: t.string, percentile: t.string }),
|
t.partial({ urlQuery: t.string, percentile: t.string }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const rumClientMetricsRoute = createRoute({
|
const rumClientMetricsRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/rum/client-metrics',
|
endpoint: 'GET /api/apm/rum/client-metrics',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: uxQueryRt,
|
query: uxQueryRt,
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
query: { urlQuery, percentile },
|
query: { urlQuery, percentile },
|
||||||
} = context.params;
|
} = resources.params;
|
||||||
|
|
||||||
return getClientMetrics({
|
return getClientMetrics({
|
||||||
setup,
|
setup,
|
||||||
|
@ -66,18 +67,18 @@ export const rumClientMetricsRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const rumPageLoadDistributionRoute = createRoute({
|
const rumPageLoadDistributionRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/rum-client/page-load-distribution',
|
endpoint: 'GET /api/apm/rum-client/page-load-distribution',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: t.intersection([uxQueryRt, percentileRangeRt]),
|
query: t.intersection([uxQueryRt, percentileRangeRt]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
query: { minPercentile, maxPercentile, urlQuery },
|
query: { minPercentile, maxPercentile, urlQuery },
|
||||||
} = context.params;
|
} = resources.params;
|
||||||
|
|
||||||
const pageLoadDistribution = await getPageLoadDistribution({
|
const pageLoadDistribution = await getPageLoadDistribution({
|
||||||
setup,
|
setup,
|
||||||
|
@ -90,7 +91,7 @@ export const rumPageLoadDistributionRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const rumPageLoadDistBreakdownRoute = createRoute({
|
const rumPageLoadDistBreakdownRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/rum-client/page-load-distribution/breakdown',
|
endpoint: 'GET /api/apm/rum-client/page-load-distribution/breakdown',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: t.intersection([
|
query: t.intersection([
|
||||||
|
@ -100,12 +101,12 @@ export const rumPageLoadDistBreakdownRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
query: { minPercentile, maxPercentile, breakdown, urlQuery },
|
query: { minPercentile, maxPercentile, breakdown, urlQuery },
|
||||||
} = context.params;
|
} = resources.params;
|
||||||
|
|
||||||
const pageLoadDistBreakdown = await getPageLoadDistBreakdown({
|
const pageLoadDistBreakdown = await getPageLoadDistBreakdown({
|
||||||
setup,
|
setup,
|
||||||
|
@ -119,18 +120,18 @@ export const rumPageLoadDistBreakdownRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const rumPageViewsTrendRoute = createRoute({
|
const rumPageViewsTrendRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/rum-client/page-view-trends',
|
endpoint: 'GET /api/apm/rum-client/page-view-trends',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: t.intersection([uxQueryRt, t.partial({ breakdowns: t.string })]),
|
query: t.intersection([uxQueryRt, t.partial({ breakdowns: t.string })]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
query: { breakdowns, urlQuery },
|
query: { breakdowns, urlQuery },
|
||||||
} = context.params;
|
} = resources.params;
|
||||||
|
|
||||||
return getPageViewTrends({
|
return getPageViewTrends({
|
||||||
setup,
|
setup,
|
||||||
|
@ -140,32 +141,32 @@ export const rumPageViewsTrendRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const rumServicesRoute = createRoute({
|
const rumServicesRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/rum-client/services',
|
endpoint: 'GET /api/apm/rum-client/services',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: t.intersection([uiFiltersRt, rangeRt]),
|
query: t.intersection([uiFiltersRt, rangeRt]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
|
|
||||||
const rumServices = await getRumServices({ setup });
|
const rumServices = await getRumServices({ setup });
|
||||||
return { rumServices };
|
return { rumServices };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const rumVisitorsBreakdownRoute = createRoute({
|
const rumVisitorsBreakdownRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/rum-client/visitor-breakdown',
|
endpoint: 'GET /api/apm/rum-client/visitor-breakdown',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: uxQueryRt,
|
query: uxQueryRt,
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
query: { urlQuery },
|
query: { urlQuery },
|
||||||
} = context.params;
|
} = resources.params;
|
||||||
|
|
||||||
return getVisitorBreakdown({
|
return getVisitorBreakdown({
|
||||||
setup,
|
setup,
|
||||||
|
@ -174,18 +175,18 @@ export const rumVisitorsBreakdownRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const rumWebCoreVitals = createRoute({
|
const rumWebCoreVitals = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/rum-client/web-core-vitals',
|
endpoint: 'GET /api/apm/rum-client/web-core-vitals',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: uxQueryRt,
|
query: uxQueryRt,
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
query: { urlQuery, percentile },
|
query: { urlQuery, percentile },
|
||||||
} = context.params;
|
} = resources.params;
|
||||||
|
|
||||||
return getWebCoreVitals({
|
return getWebCoreVitals({
|
||||||
setup,
|
setup,
|
||||||
|
@ -195,18 +196,18 @@ export const rumWebCoreVitals = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const rumLongTaskMetrics = createRoute({
|
const rumLongTaskMetrics = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/rum-client/long-task-metrics',
|
endpoint: 'GET /api/apm/rum-client/long-task-metrics',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: uxQueryRt,
|
query: uxQueryRt,
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
query: { urlQuery, percentile },
|
query: { urlQuery, percentile },
|
||||||
} = context.params;
|
} = resources.params;
|
||||||
|
|
||||||
return getLongTaskMetrics({
|
return getLongTaskMetrics({
|
||||||
setup,
|
setup,
|
||||||
|
@ -216,24 +217,24 @@ export const rumLongTaskMetrics = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const rumUrlSearch = createRoute({
|
const rumUrlSearch = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/rum-client/url-search',
|
endpoint: 'GET /api/apm/rum-client/url-search',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: uxQueryRt,
|
query: uxQueryRt,
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
query: { urlQuery, percentile },
|
query: { urlQuery, percentile },
|
||||||
} = context.params;
|
} = resources.params;
|
||||||
|
|
||||||
return getUrlSearch({ setup, urlQuery, percentile: Number(percentile) });
|
return getUrlSearch({ setup, urlQuery, percentile: Number(percentile) });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const rumJSErrors = createRoute({
|
const rumJSErrors = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/rum-client/js-errors',
|
endpoint: 'GET /api/apm/rum-client/js-errors',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: t.intersection([
|
query: t.intersection([
|
||||||
|
@ -244,12 +245,12 @@ export const rumJSErrors = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
query: { pageSize, pageIndex, urlQuery },
|
query: { pageSize, pageIndex, urlQuery },
|
||||||
} = context.params;
|
} = resources.params;
|
||||||
|
|
||||||
return getJSErrors({
|
return getJSErrors({
|
||||||
setup,
|
setup,
|
||||||
|
@ -260,14 +261,14 @@ export const rumJSErrors = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const rumHasDataRoute = createRoute({
|
const rumHasDataRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/observability_overview/has_rum_data',
|
endpoint: 'GET /api/apm/observability_overview/has_rum_data',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: t.intersection([uiFiltersRt, rangeRt]),
|
query: t.intersection([uiFiltersRt, rangeRt]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
return await hasRumData({ setup });
|
return await hasRumData({ setup });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -309,21 +310,22 @@ function createLocalFiltersRoute<
|
||||||
>;
|
>;
|
||||||
queryRt: TQueryRT;
|
queryRt: TQueryRT;
|
||||||
}) {
|
}) {
|
||||||
return createRoute({
|
return createApmServerRoute({
|
||||||
endpoint,
|
endpoint,
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: t.intersection([localUiBaseQueryRt, queryRt]),
|
query: t.intersection([localUiBaseQueryRt, queryRt]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { uiFilters } = setup;
|
const { uiFilters } = setup;
|
||||||
const { query } = context.params;
|
|
||||||
|
const { query } = resources.params;
|
||||||
|
|
||||||
const { filterNames } = query;
|
const { filterNames } = query;
|
||||||
const projection = await getProjection({
|
const projection = await getProjection({
|
||||||
query,
|
query,
|
||||||
context,
|
resources,
|
||||||
setup,
|
setup,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -339,7 +341,7 @@ function createLocalFiltersRoute<
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const rumOverviewLocalFiltersRoute = createLocalFiltersRoute({
|
const rumOverviewLocalFiltersRoute = createLocalFiltersRoute({
|
||||||
endpoint: 'GET /api/apm/rum/local_filters',
|
endpoint: 'GET /api/apm/rum/local_filters',
|
||||||
getProjection: async ({ setup }) => {
|
getProjection: async ({ setup }) => {
|
||||||
return getRumPageLoadTransactionsProjection({
|
return getRumPageLoadTransactionsProjection({
|
||||||
|
@ -357,9 +359,23 @@ type GetProjection<
|
||||||
> = ({
|
> = ({
|
||||||
query,
|
query,
|
||||||
setup,
|
setup,
|
||||||
context,
|
resources,
|
||||||
}: {
|
}: {
|
||||||
query: t.TypeOf<TQueryRT>;
|
query: t.TypeOf<TQueryRT>;
|
||||||
setup: Setup & SetupTimeRange;
|
setup: Setup & SetupTimeRange;
|
||||||
context: APMRequestHandlerContext;
|
resources: APMRouteHandlerResources;
|
||||||
}) => Promise<TProjection> | TProjection;
|
}) => Promise<TProjection> | TProjection;
|
||||||
|
|
||||||
|
export const rumRouteRepository = createApmServerRouteRepository()
|
||||||
|
.add(rumClientMetricsRoute)
|
||||||
|
.add(rumPageLoadDistributionRoute)
|
||||||
|
.add(rumPageLoadDistBreakdownRoute)
|
||||||
|
.add(rumPageViewsTrendRoute)
|
||||||
|
.add(rumServicesRoute)
|
||||||
|
.add(rumVisitorsBreakdownRoute)
|
||||||
|
.add(rumWebCoreVitals)
|
||||||
|
.add(rumLongTaskMetrics)
|
||||||
|
.add(rumUrlSearch)
|
||||||
|
.add(rumJSErrors)
|
||||||
|
.add(rumHasDataRoute)
|
||||||
|
.add(rumOverviewLocalFiltersRoute);
|
||||||
|
|
|
@ -11,13 +11,14 @@ import { invalidLicenseMessage } from '../../common/service_map';
|
||||||
import { setupRequest } from '../lib/helpers/setup_request';
|
import { setupRequest } from '../lib/helpers/setup_request';
|
||||||
import { getServiceMap } from '../lib/service_map/get_service_map';
|
import { getServiceMap } from '../lib/service_map/get_service_map';
|
||||||
import { getServiceMapServiceNodeInfo } from '../lib/service_map/get_service_map_service_node_info';
|
import { getServiceMapServiceNodeInfo } from '../lib/service_map/get_service_map_service_node_info';
|
||||||
import { createRoute } from './create_route';
|
import { createApmServerRoute } from './create_apm_server_route';
|
||||||
import { environmentRt, rangeRt } from './default_api_types';
|
import { environmentRt, rangeRt } from './default_api_types';
|
||||||
import { notifyFeatureUsage } from '../feature';
|
import { notifyFeatureUsage } from '../feature';
|
||||||
import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions';
|
import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions';
|
||||||
import { isActivePlatinumLicense } from '../../common/license_check';
|
import { isActivePlatinumLicense } from '../../common/license_check';
|
||||||
|
import { createApmServerRouteRepository } from './create_apm_server_route_repository';
|
||||||
|
|
||||||
export const serviceMapRoute = createRoute({
|
const serviceMapRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/service-map',
|
endpoint: 'GET /api/apm/service-map',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: t.intersection([
|
query: t.intersection([
|
||||||
|
@ -29,8 +30,9 @@ export const serviceMapRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
if (!context.config['xpack.apm.serviceMapEnabled']) {
|
const { config, context, params, logger } = resources;
|
||||||
|
if (!config['xpack.apm.serviceMapEnabled']) {
|
||||||
throw Boom.notFound();
|
throw Boom.notFound();
|
||||||
}
|
}
|
||||||
if (!isActivePlatinumLicense(context.licensing.license)) {
|
if (!isActivePlatinumLicense(context.licensing.license)) {
|
||||||
|
@ -42,11 +44,10 @@ export const serviceMapRoute = createRoute({
|
||||||
featureName: 'serviceMaps',
|
featureName: 'serviceMaps',
|
||||||
});
|
});
|
||||||
|
|
||||||
const logger = context.logger;
|
const setup = await setupRequest(resources);
|
||||||
const setup = await setupRequest(context, request);
|
|
||||||
const {
|
const {
|
||||||
query: { serviceName, environment },
|
query: { serviceName, environment },
|
||||||
} = context.params;
|
} = params;
|
||||||
|
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
|
@ -61,7 +62,7 @@ export const serviceMapRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const serviceMapServiceNodeRoute = createRoute({
|
const serviceMapServiceNodeRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/service-map/service/{serviceName}',
|
endpoint: 'GET /api/apm/service-map/service/{serviceName}',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({
|
path: t.type({
|
||||||
|
@ -70,19 +71,21 @@ export const serviceMapServiceNodeRoute = createRoute({
|
||||||
query: t.intersection([environmentRt, rangeRt]),
|
query: t.intersection([environmentRt, rangeRt]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
if (!context.config['xpack.apm.serviceMapEnabled']) {
|
const { config, context, params } = resources;
|
||||||
|
|
||||||
|
if (!config['xpack.apm.serviceMapEnabled']) {
|
||||||
throw Boom.notFound();
|
throw Boom.notFound();
|
||||||
}
|
}
|
||||||
if (!isActivePlatinumLicense(context.licensing.license)) {
|
if (!isActivePlatinumLicense(context.licensing.license)) {
|
||||||
throw Boom.forbidden(invalidLicenseMessage);
|
throw Boom.forbidden(invalidLicenseMessage);
|
||||||
}
|
}
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
path: { serviceName },
|
path: { serviceName },
|
||||||
query: { environment },
|
query: { environment },
|
||||||
} = context.params;
|
} = params;
|
||||||
|
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
|
@ -96,3 +99,7 @@ export const serviceMapServiceNodeRoute = createRoute({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const serviceMapRouteRepository = createApmServerRouteRepository()
|
||||||
|
.add(serviceMapRoute)
|
||||||
|
.add(serviceMapServiceNodeRoute);
|
||||||
|
|
|
@ -6,12 +6,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as t from 'io-ts';
|
import * as t from 'io-ts';
|
||||||
import { createRoute } from './create_route';
|
import { createApmServerRouteRepository } from './create_apm_server_route_repository';
|
||||||
|
import { createApmServerRoute } from './create_apm_server_route';
|
||||||
import { setupRequest } from '../lib/helpers/setup_request';
|
import { setupRequest } from '../lib/helpers/setup_request';
|
||||||
import { getServiceNodes } from '../lib/service_nodes';
|
import { getServiceNodes } from '../lib/service_nodes';
|
||||||
import { rangeRt, kueryRt } from './default_api_types';
|
import { rangeRt, kueryRt } from './default_api_types';
|
||||||
|
|
||||||
export const serviceNodesRoute = createRoute({
|
const serviceNodesRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/services/{serviceName}/serviceNodes',
|
endpoint: 'GET /api/apm/services/{serviceName}/serviceNodes',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({
|
path: t.type({
|
||||||
|
@ -20,9 +21,9 @@ export const serviceNodesRoute = createRoute({
|
||||||
query: t.intersection([kueryRt, rangeRt]),
|
query: t.intersection([kueryRt, rangeRt]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { params } = context;
|
const { params } = resources;
|
||||||
const { serviceName } = params.path;
|
const { serviceName } = params.path;
|
||||||
const { kuery } = params.query;
|
const { kuery } = params.query;
|
||||||
|
|
||||||
|
@ -30,3 +31,7 @@ export const serviceNodesRoute = createRoute({
|
||||||
return { serviceNodes };
|
return { serviceNodes };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const serviceNodeRouteRepository = createApmServerRouteRepository().add(
|
||||||
|
serviceNodesRoute
|
||||||
|
);
|
||||||
|
|
|
@ -6,15 +6,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Boom from '@hapi/boom';
|
import Boom from '@hapi/boom';
|
||||||
|
import { jsonRt } from '@kbn/io-ts-utils';
|
||||||
import * as t from 'io-ts';
|
import * as t from 'io-ts';
|
||||||
import { uniq } from 'lodash';
|
import { uniq } from 'lodash';
|
||||||
import {
|
import { latencyAggregationTypeRt } from '../../common/latency_aggregation_types';
|
||||||
LatencyAggregationType,
|
|
||||||
latencyAggregationTypeRt,
|
|
||||||
} from '../../common/latency_aggregation_types';
|
|
||||||
import { ProfilingValueType } from '../../common/profiling';
|
import { ProfilingValueType } from '../../common/profiling';
|
||||||
import { isoToEpochRt } from '../../common/runtime_types/iso_to_epoch_rt';
|
import { isoToEpochRt } from '../../common/runtime_types/iso_to_epoch_rt';
|
||||||
import { jsonRt } from '../../common/runtime_types/json_rt';
|
|
||||||
import { toNumberRt } from '../../common/runtime_types/to_number_rt';
|
import { toNumberRt } from '../../common/runtime_types/to_number_rt';
|
||||||
import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions';
|
import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions';
|
||||||
import { setupRequest } from '../lib/helpers/setup_request';
|
import { setupRequest } from '../lib/helpers/setup_request';
|
||||||
|
@ -35,7 +32,8 @@ import { getServiceProfilingStatistics } from '../lib/services/profiling/get_ser
|
||||||
import { getServiceProfilingTimeline } from '../lib/services/profiling/get_service_profiling_timeline';
|
import { getServiceProfilingTimeline } from '../lib/services/profiling/get_service_profiling_timeline';
|
||||||
import { offsetPreviousPeriodCoordinates } from '../utils/offset_previous_period_coordinate';
|
import { offsetPreviousPeriodCoordinates } from '../utils/offset_previous_period_coordinate';
|
||||||
import { withApmSpan } from '../utils/with_apm_span';
|
import { withApmSpan } from '../utils/with_apm_span';
|
||||||
import { createRoute } from './create_route';
|
import { createApmServerRoute } from './create_apm_server_route';
|
||||||
|
import { createApmServerRouteRepository } from './create_apm_server_route_repository';
|
||||||
import {
|
import {
|
||||||
comparisonRangeRt,
|
comparisonRangeRt,
|
||||||
environmentRt,
|
environmentRt,
|
||||||
|
@ -43,15 +41,16 @@ import {
|
||||||
rangeRt,
|
rangeRt,
|
||||||
} from './default_api_types';
|
} from './default_api_types';
|
||||||
|
|
||||||
export const servicesRoute = createRoute({
|
const servicesRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/services',
|
endpoint: 'GET /api/apm/services',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: t.intersection([environmentRt, kueryRt, rangeRt]),
|
query: t.intersection([environmentRt, kueryRt, rangeRt]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { environment, kuery } = context.params.query;
|
const { params, logger } = resources;
|
||||||
|
const { environment, kuery } = params.query;
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
);
|
);
|
||||||
|
@ -61,21 +60,22 @@ export const servicesRoute = createRoute({
|
||||||
kuery,
|
kuery,
|
||||||
setup,
|
setup,
|
||||||
searchAggregatedTransactions,
|
searchAggregatedTransactions,
|
||||||
logger: context.logger,
|
logger,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const serviceMetadataDetailsRoute = createRoute({
|
const serviceMetadataDetailsRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/services/{serviceName}/metadata/details',
|
endpoint: 'GET /api/apm/services/{serviceName}/metadata/details',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({ serviceName: t.string }),
|
path: t.type({ serviceName: t.string }),
|
||||||
query: rangeRt,
|
query: rangeRt,
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { serviceName } = context.params.path;
|
const { params } = resources;
|
||||||
|
const { serviceName } = params.path;
|
||||||
|
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
|
@ -89,16 +89,17 @@ export const serviceMetadataDetailsRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const serviceMetadataIconsRoute = createRoute({
|
const serviceMetadataIconsRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/services/{serviceName}/metadata/icons',
|
endpoint: 'GET /api/apm/services/{serviceName}/metadata/icons',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({ serviceName: t.string }),
|
path: t.type({ serviceName: t.string }),
|
||||||
query: rangeRt,
|
query: rangeRt,
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { serviceName } = context.params.path;
|
const { params } = resources;
|
||||||
|
const { serviceName } = params.path;
|
||||||
|
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
|
@ -112,7 +113,7 @@ export const serviceMetadataIconsRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const serviceAgentNameRoute = createRoute({
|
const serviceAgentNameRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/services/{serviceName}/agent_name',
|
endpoint: 'GET /api/apm/services/{serviceName}/agent_name',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({
|
path: t.type({
|
||||||
|
@ -121,9 +122,10 @@ export const serviceAgentNameRoute = createRoute({
|
||||||
query: rangeRt,
|
query: rangeRt,
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { serviceName } = context.params.path;
|
const { params } = resources;
|
||||||
|
const { serviceName } = params.path;
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
);
|
);
|
||||||
|
@ -136,7 +138,7 @@ export const serviceAgentNameRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const serviceTransactionTypesRoute = createRoute({
|
const serviceTransactionTypesRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/services/{serviceName}/transaction_types',
|
endpoint: 'GET /api/apm/services/{serviceName}/transaction_types',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({
|
path: t.type({
|
||||||
|
@ -145,9 +147,11 @@ export const serviceTransactionTypesRoute = createRoute({
|
||||||
query: rangeRt,
|
query: rangeRt,
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { serviceName } = context.params.path;
|
const { params } = resources;
|
||||||
|
const { serviceName } = params.path;
|
||||||
|
|
||||||
return getServiceTransactionTypes({
|
return getServiceTransactionTypes({
|
||||||
serviceName,
|
serviceName,
|
||||||
setup,
|
setup,
|
||||||
|
@ -158,7 +162,7 @@ export const serviceTransactionTypesRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const serviceNodeMetadataRoute = createRoute({
|
const serviceNodeMetadataRoute = createApmServerRoute({
|
||||||
endpoint:
|
endpoint:
|
||||||
'GET /api/apm/services/{serviceName}/node/{serviceNodeName}/metadata',
|
'GET /api/apm/services/{serviceName}/node/{serviceNodeName}/metadata',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
|
@ -169,10 +173,11 @@ export const serviceNodeMetadataRoute = createRoute({
|
||||||
query: t.intersection([kueryRt, rangeRt]),
|
query: t.intersection([kueryRt, rangeRt]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { serviceName, serviceNodeName } = context.params.path;
|
const { params } = resources;
|
||||||
const { kuery } = context.params.query;
|
const { serviceName, serviceNodeName } = params.path;
|
||||||
|
const { kuery } = params.query;
|
||||||
|
|
||||||
return getServiceNodeMetadata({
|
return getServiceNodeMetadata({
|
||||||
kuery,
|
kuery,
|
||||||
|
@ -183,7 +188,7 @@ export const serviceNodeMetadataRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const serviceAnnotationsRoute = createRoute({
|
const serviceAnnotationsRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/services/{serviceName}/annotation/search',
|
endpoint: 'GET /api/apm/services/{serviceName}/annotation/search',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({
|
path: t.type({
|
||||||
|
@ -192,12 +197,13 @@ export const serviceAnnotationsRoute = createRoute({
|
||||||
query: t.intersection([environmentRt, rangeRt]),
|
query: t.intersection([environmentRt, rangeRt]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { serviceName } = context.params.path;
|
const { params, plugins, context, request, logger } = resources;
|
||||||
const { environment } = context.params.query;
|
const { serviceName } = params.path;
|
||||||
|
const { environment } = params.query;
|
||||||
|
|
||||||
const { observability } = context.plugins;
|
const { observability } = plugins;
|
||||||
|
|
||||||
const [
|
const [
|
||||||
annotationsClient,
|
annotationsClient,
|
||||||
|
@ -205,7 +211,7 @@ export const serviceAnnotationsRoute = createRoute({
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
observability
|
observability
|
||||||
? withApmSpan('get_scoped_annotations_client', () =>
|
? withApmSpan('get_scoped_annotations_client', () =>
|
||||||
observability.getScopedAnnotationsClient(context, request)
|
observability.setup.getScopedAnnotationsClient(context, request)
|
||||||
)
|
)
|
||||||
: undefined,
|
: undefined,
|
||||||
getSearchAggregatedTransactions(setup),
|
getSearchAggregatedTransactions(setup),
|
||||||
|
@ -218,12 +224,12 @@ export const serviceAnnotationsRoute = createRoute({
|
||||||
serviceName,
|
serviceName,
|
||||||
annotationsClient,
|
annotationsClient,
|
||||||
client: context.core.elasticsearch.client.asCurrentUser,
|
client: context.core.elasticsearch.client.asCurrentUser,
|
||||||
logger: context.logger,
|
logger,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const serviceAnnotationsCreateRoute = createRoute({
|
const serviceAnnotationsCreateRoute = createApmServerRoute({
|
||||||
endpoint: 'POST /api/apm/services/{serviceName}/annotation',
|
endpoint: 'POST /api/apm/services/{serviceName}/annotation',
|
||||||
options: {
|
options: {
|
||||||
tags: ['access:apm', 'access:apm_write'],
|
tags: ['access:apm', 'access:apm_write'],
|
||||||
|
@ -250,12 +256,17 @@ export const serviceAnnotationsCreateRoute = createRoute({
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
handler: async ({ request, context }) => {
|
handler: async (resources) => {
|
||||||
const { observability } = context.plugins;
|
const {
|
||||||
|
request,
|
||||||
|
context,
|
||||||
|
plugins: { observability },
|
||||||
|
params,
|
||||||
|
} = resources;
|
||||||
|
|
||||||
const annotationsClient = observability
|
const annotationsClient = observability
|
||||||
? await withApmSpan('get_scoped_annotations_client', () =>
|
? await withApmSpan('get_scoped_annotations_client', () =>
|
||||||
observability.getScopedAnnotationsClient(context, request)
|
observability.setup.getScopedAnnotationsClient(context, request)
|
||||||
)
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
@ -263,7 +274,7 @@ export const serviceAnnotationsCreateRoute = createRoute({
|
||||||
throw Boom.notFound();
|
throw Boom.notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { body, path } = context.params;
|
const { body, path } = params;
|
||||||
|
|
||||||
return withApmSpan('create_annotation', () =>
|
return withApmSpan('create_annotation', () =>
|
||||||
annotationsClient.create({
|
annotationsClient.create({
|
||||||
|
@ -283,7 +294,7 @@ export const serviceAnnotationsCreateRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const serviceErrorGroupsPrimaryStatisticsRoute = createRoute({
|
const serviceErrorGroupsPrimaryStatisticsRoute = createApmServerRoute({
|
||||||
endpoint:
|
endpoint:
|
||||||
'GET /api/apm/services/{serviceName}/error_groups/primary_statistics',
|
'GET /api/apm/services/{serviceName}/error_groups/primary_statistics',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
|
@ -300,13 +311,14 @@ export const serviceErrorGroupsPrimaryStatisticsRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
|
const { params } = resources;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
path: { serviceName },
|
path: { serviceName },
|
||||||
query: { kuery, transactionType, environment },
|
query: { kuery, transactionType, environment },
|
||||||
} = context.params;
|
} = params;
|
||||||
return getServiceErrorGroupPrimaryStatistics({
|
return getServiceErrorGroupPrimaryStatistics({
|
||||||
kuery,
|
kuery,
|
||||||
serviceName,
|
serviceName,
|
||||||
|
@ -317,7 +329,7 @@ export const serviceErrorGroupsPrimaryStatisticsRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const serviceErrorGroupsComparisonStatisticsRoute = createRoute({
|
const serviceErrorGroupsComparisonStatisticsRoute = createApmServerRoute({
|
||||||
endpoint:
|
endpoint:
|
||||||
'GET /api/apm/services/{serviceName}/error_groups/comparison_statistics',
|
'GET /api/apm/services/{serviceName}/error_groups/comparison_statistics',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
|
@ -337,8 +349,9 @@ export const serviceErrorGroupsComparisonStatisticsRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
|
const { params } = resources;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
path: { serviceName },
|
path: { serviceName },
|
||||||
|
@ -351,7 +364,7 @@ export const serviceErrorGroupsComparisonStatisticsRoute = createRoute({
|
||||||
comparisonStart,
|
comparisonStart,
|
||||||
comparisonEnd,
|
comparisonEnd,
|
||||||
},
|
},
|
||||||
} = context.params;
|
} = params;
|
||||||
|
|
||||||
return getServiceErrorGroupPeriods({
|
return getServiceErrorGroupPeriods({
|
||||||
environment,
|
environment,
|
||||||
|
@ -367,7 +380,7 @@ export const serviceErrorGroupsComparisonStatisticsRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const serviceThroughputRoute = createRoute({
|
const serviceThroughputRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/services/{serviceName}/throughput',
|
endpoint: 'GET /api/apm/services/{serviceName}/throughput',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({
|
path: t.type({
|
||||||
|
@ -382,16 +395,17 @@ export const serviceThroughputRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { serviceName } = context.params.path;
|
const { params } = resources;
|
||||||
|
const { serviceName } = params.path;
|
||||||
const {
|
const {
|
||||||
environment,
|
environment,
|
||||||
kuery,
|
kuery,
|
||||||
transactionType,
|
transactionType,
|
||||||
comparisonStart,
|
comparisonStart,
|
||||||
comparisonEnd,
|
comparisonEnd,
|
||||||
} = context.params.query;
|
} = params.query;
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
);
|
);
|
||||||
|
@ -432,7 +446,7 @@ export const serviceThroughputRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const serviceInstancesPrimaryStatisticsRoute = createRoute({
|
const serviceInstancesPrimaryStatisticsRoute = createApmServerRoute({
|
||||||
endpoint:
|
endpoint:
|
||||||
'GET /api/apm/services/{serviceName}/service_overview_instances/primary_statistics',
|
'GET /api/apm/services/{serviceName}/service_overview_instances/primary_statistics',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
|
@ -450,12 +464,16 @@ export const serviceInstancesPrimaryStatisticsRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { serviceName } = context.params.path;
|
const { params } = resources;
|
||||||
const { environment, kuery, transactionType } = context.params.query;
|
const { serviceName } = params.path;
|
||||||
const latencyAggregationType = (context.params.query
|
const {
|
||||||
.latencyAggregationType as unknown) as LatencyAggregationType;
|
environment,
|
||||||
|
kuery,
|
||||||
|
transactionType,
|
||||||
|
latencyAggregationType,
|
||||||
|
} = params.query;
|
||||||
|
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
|
@ -479,7 +497,7 @@ export const serviceInstancesPrimaryStatisticsRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const serviceInstancesComparisonStatisticsRoute = createRoute({
|
const serviceInstancesComparisonStatisticsRoute = createApmServerRoute({
|
||||||
endpoint:
|
endpoint:
|
||||||
'GET /api/apm/services/{serviceName}/service_overview_instances/comparison_statistics',
|
'GET /api/apm/services/{serviceName}/service_overview_instances/comparison_statistics',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
|
@ -500,9 +518,10 @@ export const serviceInstancesComparisonStatisticsRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { serviceName } = context.params.path;
|
const { params } = resources;
|
||||||
|
const { serviceName } = params.path;
|
||||||
const {
|
const {
|
||||||
environment,
|
environment,
|
||||||
kuery,
|
kuery,
|
||||||
|
@ -511,9 +530,8 @@ export const serviceInstancesComparisonStatisticsRoute = createRoute({
|
||||||
comparisonEnd,
|
comparisonEnd,
|
||||||
serviceNodeIds,
|
serviceNodeIds,
|
||||||
numBuckets,
|
numBuckets,
|
||||||
} = context.params.query;
|
latencyAggregationType,
|
||||||
const latencyAggregationType = (context.params.query
|
} = params.query;
|
||||||
.latencyAggregationType as unknown) as LatencyAggregationType;
|
|
||||||
|
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
|
@ -535,7 +553,7 @@ export const serviceInstancesComparisonStatisticsRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const serviceDependenciesRoute = createRoute({
|
const serviceDependenciesRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/services/{serviceName}/dependencies',
|
endpoint: 'GET /api/apm/services/{serviceName}/dependencies',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({
|
path: t.type({
|
||||||
|
@ -552,11 +570,11 @@ export const serviceDependenciesRoute = createRoute({
|
||||||
options: {
|
options: {
|
||||||
tags: ['access:apm'],
|
tags: ['access:apm'],
|
||||||
},
|
},
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
|
const { params } = resources;
|
||||||
const { serviceName } = context.params.path;
|
const { serviceName } = params.path;
|
||||||
const { environment, numBuckets } = context.params.query;
|
const { environment, numBuckets } = params.query;
|
||||||
|
|
||||||
const serviceDependencies = await getServiceDependencies({
|
const serviceDependencies = await getServiceDependencies({
|
||||||
serviceName,
|
serviceName,
|
||||||
|
@ -569,7 +587,7 @@ export const serviceDependenciesRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const serviceProfilingTimelineRoute = createRoute({
|
const serviceProfilingTimelineRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/services/{serviceName}/profiling/timeline',
|
endpoint: 'GET /api/apm/services/{serviceName}/profiling/timeline',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({
|
path: t.type({
|
||||||
|
@ -580,13 +598,13 @@ export const serviceProfilingTimelineRoute = createRoute({
|
||||||
options: {
|
options: {
|
||||||
tags: ['access:apm'],
|
tags: ['access:apm'],
|
||||||
},
|
},
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
|
const { params } = resources;
|
||||||
const {
|
const {
|
||||||
path: { serviceName },
|
path: { serviceName },
|
||||||
query: { environment, kuery },
|
query: { environment, kuery },
|
||||||
} = context.params;
|
} = params;
|
||||||
|
|
||||||
const profilingTimeline = await getServiceProfilingTimeline({
|
const profilingTimeline = await getServiceProfilingTimeline({
|
||||||
kuery,
|
kuery,
|
||||||
|
@ -599,7 +617,7 @@ export const serviceProfilingTimelineRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const serviceProfilingStatisticsRoute = createRoute({
|
const serviceProfilingStatisticsRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/services/{serviceName}/profiling/statistics',
|
endpoint: 'GET /api/apm/services/{serviceName}/profiling/statistics',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({
|
path: t.type({
|
||||||
|
@ -625,13 +643,15 @@ export const serviceProfilingStatisticsRoute = createRoute({
|
||||||
options: {
|
options: {
|
||||||
tags: ['access:apm'],
|
tags: ['access:apm'],
|
||||||
},
|
},
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
|
|
||||||
|
const { params, logger } = resources;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
path: { serviceName },
|
path: { serviceName },
|
||||||
query: { environment, kuery, valueType },
|
query: { environment, kuery, valueType },
|
||||||
} = context.params;
|
} = params;
|
||||||
|
|
||||||
return getServiceProfilingStatistics({
|
return getServiceProfilingStatistics({
|
||||||
kuery,
|
kuery,
|
||||||
|
@ -639,7 +659,25 @@ export const serviceProfilingStatisticsRoute = createRoute({
|
||||||
environment,
|
environment,
|
||||||
valueType,
|
valueType,
|
||||||
setup,
|
setup,
|
||||||
logger: context.logger,
|
logger,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const serviceRouteRepository = createApmServerRouteRepository()
|
||||||
|
.add(servicesRoute)
|
||||||
|
.add(serviceMetadataDetailsRoute)
|
||||||
|
.add(serviceMetadataIconsRoute)
|
||||||
|
.add(serviceAgentNameRoute)
|
||||||
|
.add(serviceTransactionTypesRoute)
|
||||||
|
.add(serviceNodeMetadataRoute)
|
||||||
|
.add(serviceAnnotationsRoute)
|
||||||
|
.add(serviceAnnotationsCreateRoute)
|
||||||
|
.add(serviceErrorGroupsPrimaryStatisticsRoute)
|
||||||
|
.add(serviceErrorGroupsComparisonStatisticsRoute)
|
||||||
|
.add(serviceThroughputRoute)
|
||||||
|
.add(serviceInstancesPrimaryStatisticsRoute)
|
||||||
|
.add(serviceInstancesComparisonStatisticsRoute)
|
||||||
|
.add(serviceDependenciesRoute)
|
||||||
|
.add(serviceProfilingTimelineRoute)
|
||||||
|
.add(serviceProfilingStatisticsRoute);
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { findExactConfiguration } from '../../lib/settings/agent_configuration/f
|
||||||
import { listConfigurations } from '../../lib/settings/agent_configuration/list_configurations';
|
import { listConfigurations } from '../../lib/settings/agent_configuration/list_configurations';
|
||||||
import { getEnvironments } from '../../lib/settings/agent_configuration/get_environments';
|
import { getEnvironments } from '../../lib/settings/agent_configuration/get_environments';
|
||||||
import { deleteConfiguration } from '../../lib/settings/agent_configuration/delete_configuration';
|
import { deleteConfiguration } from '../../lib/settings/agent_configuration/delete_configuration';
|
||||||
import { createRoute } from '../create_route';
|
import { createApmServerRoute } from '../create_apm_server_route';
|
||||||
import { getAgentNameByService } from '../../lib/settings/agent_configuration/get_agent_name_by_service';
|
import { getAgentNameByService } from '../../lib/settings/agent_configuration/get_agent_name_by_service';
|
||||||
import { markAppliedByAgent } from '../../lib/settings/agent_configuration/mark_applied_by_agent';
|
import { markAppliedByAgent } from '../../lib/settings/agent_configuration/mark_applied_by_agent';
|
||||||
import {
|
import {
|
||||||
|
@ -24,34 +24,37 @@ import {
|
||||||
agentConfigurationIntakeRt,
|
agentConfigurationIntakeRt,
|
||||||
} from '../../../common/agent_configuration/runtime_types/agent_configuration_intake_rt';
|
} from '../../../common/agent_configuration/runtime_types/agent_configuration_intake_rt';
|
||||||
import { getSearchAggregatedTransactions } from '../../lib/helpers/aggregated_transactions';
|
import { getSearchAggregatedTransactions } from '../../lib/helpers/aggregated_transactions';
|
||||||
|
import { createApmServerRouteRepository } from '../create_apm_server_route_repository';
|
||||||
|
|
||||||
// get list of configurations
|
// get list of configurations
|
||||||
export const agentConfigurationRoute = createRoute({
|
const agentConfigurationRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/settings/agent-configuration',
|
endpoint: 'GET /api/apm/settings/agent-configuration',
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const configurations = await listConfigurations({ setup });
|
const configurations = await listConfigurations({ setup });
|
||||||
return { configurations };
|
return { configurations };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// get a single configuration
|
// get a single configuration
|
||||||
export const getSingleAgentConfigurationRoute = createRoute({
|
const getSingleAgentConfigurationRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/settings/agent-configuration/view',
|
endpoint: 'GET /api/apm/settings/agent-configuration/view',
|
||||||
params: t.partial({
|
params: t.partial({
|
||||||
query: serviceRt,
|
query: serviceRt,
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { name, environment } = context.params.query;
|
const { params, logger } = resources;
|
||||||
|
|
||||||
|
const { name, environment } = params.query;
|
||||||
|
|
||||||
const service = { name, environment };
|
const service = { name, environment };
|
||||||
const config = await findExactConfiguration({ service, setup });
|
const config = await findExactConfiguration({ service, setup });
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
context.logger.info(
|
logger.info(
|
||||||
`Config was not found for ${service.name}/${service.environment}`
|
`Config was not found for ${service.name}/${service.environment}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -63,7 +66,7 @@ export const getSingleAgentConfigurationRoute = createRoute({
|
||||||
});
|
});
|
||||||
|
|
||||||
// delete configuration
|
// delete configuration
|
||||||
export const deleteAgentConfigurationRoute = createRoute({
|
const deleteAgentConfigurationRoute = createApmServerRoute({
|
||||||
endpoint: 'DELETE /api/apm/settings/agent-configuration',
|
endpoint: 'DELETE /api/apm/settings/agent-configuration',
|
||||||
options: {
|
options: {
|
||||||
tags: ['access:apm', 'access:apm_write'],
|
tags: ['access:apm', 'access:apm_write'],
|
||||||
|
@ -73,20 +76,22 @@ export const deleteAgentConfigurationRoute = createRoute({
|
||||||
service: serviceRt,
|
service: serviceRt,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { service } = context.params.body;
|
const { params, logger } = resources;
|
||||||
|
|
||||||
|
const { service } = params.body;
|
||||||
|
|
||||||
const config = await findExactConfiguration({ service, setup });
|
const config = await findExactConfiguration({ service, setup });
|
||||||
if (!config) {
|
if (!config) {
|
||||||
context.logger.info(
|
logger.info(
|
||||||
`Config was not found for ${service.name}/${service.environment}`
|
`Config was not found for ${service.name}/${service.environment}`
|
||||||
);
|
);
|
||||||
|
|
||||||
throw Boom.notFound();
|
throw Boom.notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
context.logger.info(
|
logger.info(
|
||||||
`Deleting config ${service.name}/${service.environment} (${config._id})`
|
`Deleting config ${service.name}/${service.environment} (${config._id})`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -98,7 +103,7 @@ export const deleteAgentConfigurationRoute = createRoute({
|
||||||
});
|
});
|
||||||
|
|
||||||
// create/update configuration
|
// create/update configuration
|
||||||
export const createOrUpdateAgentConfigurationRoute = createRoute({
|
const createOrUpdateAgentConfigurationRoute = createApmServerRoute({
|
||||||
endpoint: 'PUT /api/apm/settings/agent-configuration',
|
endpoint: 'PUT /api/apm/settings/agent-configuration',
|
||||||
options: {
|
options: {
|
||||||
tags: ['access:apm', 'access:apm_write'],
|
tags: ['access:apm', 'access:apm_write'],
|
||||||
|
@ -107,9 +112,10 @@ export const createOrUpdateAgentConfigurationRoute = createRoute({
|
||||||
t.partial({ query: t.partial({ overwrite: toBooleanRt }) }),
|
t.partial({ query: t.partial({ overwrite: toBooleanRt }) }),
|
||||||
t.type({ body: agentConfigurationIntakeRt }),
|
t.type({ body: agentConfigurationIntakeRt }),
|
||||||
]),
|
]),
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { body, query } = context.params;
|
const { params, logger } = resources;
|
||||||
|
const { body, query } = params;
|
||||||
|
|
||||||
// if the config already exists, it is fetched and updated
|
// if the config already exists, it is fetched and updated
|
||||||
// this is to avoid creating two configs with identical service params
|
// this is to avoid creating two configs with identical service params
|
||||||
|
@ -125,13 +131,13 @@ export const createOrUpdateAgentConfigurationRoute = createRoute({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.logger.info(
|
logger.info(
|
||||||
`${config ? 'Updating' : 'Creating'} config ${body.service.name}/${
|
`${config ? 'Updating' : 'Creating'} config ${body.service.name}/${
|
||||||
body.service.environment
|
body.service.environment
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
|
|
||||||
return await createOrUpdateConfiguration({
|
await createOrUpdateConfiguration({
|
||||||
configurationId: config?._id,
|
configurationId: config?._id,
|
||||||
configurationIntake: body,
|
configurationIntake: body,
|
||||||
setup,
|
setup,
|
||||||
|
@ -147,35 +153,35 @@ const searchParamsRt = t.intersection([
|
||||||
export type AgentConfigSearchParams = t.TypeOf<typeof searchParamsRt>;
|
export type AgentConfigSearchParams = t.TypeOf<typeof searchParamsRt>;
|
||||||
|
|
||||||
// Lookup single configuration (used by APM Server)
|
// Lookup single configuration (used by APM Server)
|
||||||
export const agentConfigurationSearchRoute = createRoute({
|
const agentConfigurationSearchRoute = createApmServerRoute({
|
||||||
endpoint: 'POST /api/apm/settings/agent-configuration/search',
|
endpoint: 'POST /api/apm/settings/agent-configuration/search',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
body: searchParamsRt,
|
body: searchParamsRt,
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
|
const { params, logger } = resources;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
service,
|
service,
|
||||||
etag,
|
etag,
|
||||||
mark_as_applied_by_agent: markAsAppliedByAgent,
|
mark_as_applied_by_agent: markAsAppliedByAgent,
|
||||||
} = context.params.body;
|
} = params.body;
|
||||||
|
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const config = await searchConfigurations({
|
const config = await searchConfigurations({
|
||||||
service,
|
service,
|
||||||
setup,
|
setup,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
context.logger.debug(
|
logger.debug(
|
||||||
`[Central configuration] Config was not found for ${service.name}/${service.environment}`
|
`[Central configuration] Config was not found for ${service.name}/${service.environment}`
|
||||||
);
|
);
|
||||||
throw Boom.notFound();
|
throw Boom.notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
context.logger.info(
|
logger.info(`Config was found for ${service.name}/${service.environment}`);
|
||||||
`Config was found for ${service.name}/${service.environment}`
|
|
||||||
);
|
|
||||||
|
|
||||||
// update `applied_by_agent` field
|
// update `applied_by_agent` field
|
||||||
// when `markAsAppliedByAgent` is true (Jaeger agent doesn't have etags)
|
// when `markAsAppliedByAgent` is true (Jaeger agent doesn't have etags)
|
||||||
|
@ -197,11 +203,11 @@ export const agentConfigurationSearchRoute = createRoute({
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// get list of services
|
// get list of services
|
||||||
export const listAgentConfigurationServicesRoute = createRoute({
|
const listAgentConfigurationServicesRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/settings/agent-configuration/services',
|
endpoint: 'GET /api/apm/settings/agent-configuration/services',
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
);
|
);
|
||||||
|
@ -215,15 +221,17 @@ export const listAgentConfigurationServicesRoute = createRoute({
|
||||||
});
|
});
|
||||||
|
|
||||||
// get environments for service
|
// get environments for service
|
||||||
export const listAgentConfigurationEnvironmentsRoute = createRoute({
|
const listAgentConfigurationEnvironmentsRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/settings/agent-configuration/environments',
|
endpoint: 'GET /api/apm/settings/agent-configuration/environments',
|
||||||
params: t.partial({
|
params: t.partial({
|
||||||
query: t.partial({ serviceName: t.string }),
|
query: t.partial({ serviceName: t.string }),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { serviceName } = context.params.query;
|
const { params } = resources;
|
||||||
|
|
||||||
|
const { serviceName } = params.query;
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
);
|
);
|
||||||
|
@ -239,16 +247,27 @@ export const listAgentConfigurationEnvironmentsRoute = createRoute({
|
||||||
});
|
});
|
||||||
|
|
||||||
// get agentName for service
|
// get agentName for service
|
||||||
export const agentConfigurationAgentNameRoute = createRoute({
|
const agentConfigurationAgentNameRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/settings/agent-configuration/agent_name',
|
endpoint: 'GET /api/apm/settings/agent-configuration/agent_name',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: t.type({ serviceName: t.string }),
|
query: t.type({ serviceName: t.string }),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { serviceName } = context.params.query;
|
const { params } = resources;
|
||||||
|
const { serviceName } = params.query;
|
||||||
const agentName = await getAgentNameByService({ serviceName, setup });
|
const agentName = await getAgentNameByService({ serviceName, setup });
|
||||||
return { agentName };
|
return { agentName };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const agentConfigurationRouteRepository = createApmServerRouteRepository()
|
||||||
|
.add(agentConfigurationRoute)
|
||||||
|
.add(getSingleAgentConfigurationRoute)
|
||||||
|
.add(deleteAgentConfigurationRoute)
|
||||||
|
.add(createOrUpdateAgentConfigurationRoute)
|
||||||
|
.add(agentConfigurationSearchRoute)
|
||||||
|
.add(listAgentConfigurationServicesRoute)
|
||||||
|
.add(listAgentConfigurationEnvironmentsRoute)
|
||||||
|
.add(agentConfigurationAgentNameRoute);
|
||||||
|
|
|
@ -9,7 +9,7 @@ import * as t from 'io-ts';
|
||||||
import Boom from '@hapi/boom';
|
import Boom from '@hapi/boom';
|
||||||
import { isActivePlatinumLicense } from '../../../common/license_check';
|
import { isActivePlatinumLicense } from '../../../common/license_check';
|
||||||
import { ML_ERRORS } from '../../../common/anomaly_detection';
|
import { ML_ERRORS } from '../../../common/anomaly_detection';
|
||||||
import { createRoute } from '../create_route';
|
import { createApmServerRoute } from '../create_apm_server_route';
|
||||||
import { getAnomalyDetectionJobs } from '../../lib/anomaly_detection/get_anomaly_detection_jobs';
|
import { getAnomalyDetectionJobs } from '../../lib/anomaly_detection/get_anomaly_detection_jobs';
|
||||||
import { createAnomalyDetectionJobs } from '../../lib/anomaly_detection/create_anomaly_detection_jobs';
|
import { createAnomalyDetectionJobs } from '../../lib/anomaly_detection/create_anomaly_detection_jobs';
|
||||||
import { setupRequest } from '../../lib/helpers/setup_request';
|
import { setupRequest } from '../../lib/helpers/setup_request';
|
||||||
|
@ -18,15 +18,17 @@ import { hasLegacyJobs } from '../../lib/anomaly_detection/has_legacy_jobs';
|
||||||
import { getSearchAggregatedTransactions } from '../../lib/helpers/aggregated_transactions';
|
import { getSearchAggregatedTransactions } from '../../lib/helpers/aggregated_transactions';
|
||||||
import { notifyFeatureUsage } from '../../feature';
|
import { notifyFeatureUsage } from '../../feature';
|
||||||
import { withApmSpan } from '../../utils/with_apm_span';
|
import { withApmSpan } from '../../utils/with_apm_span';
|
||||||
|
import { createApmServerRouteRepository } from '../create_apm_server_route_repository';
|
||||||
|
|
||||||
// get ML anomaly detection jobs for each environment
|
// get ML anomaly detection jobs for each environment
|
||||||
export const anomalyDetectionJobsRoute = createRoute({
|
const anomalyDetectionJobsRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/settings/anomaly-detection/jobs',
|
endpoint: 'GET /api/apm/settings/anomaly-detection/jobs',
|
||||||
options: {
|
options: {
|
||||||
tags: ['access:apm', 'access:ml:canGetJobs'],
|
tags: ['access:apm', 'access:ml:canGetJobs'],
|
||||||
},
|
},
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
|
const { context, logger } = resources;
|
||||||
|
|
||||||
if (!isActivePlatinumLicense(context.licensing.license)) {
|
if (!isActivePlatinumLicense(context.licensing.license)) {
|
||||||
throw Boom.forbidden(ML_ERRORS.INVALID_LICENSE);
|
throw Boom.forbidden(ML_ERRORS.INVALID_LICENSE);
|
||||||
|
@ -34,7 +36,7 @@ export const anomalyDetectionJobsRoute = createRoute({
|
||||||
|
|
||||||
const [jobs, legacyJobs] = await withApmSpan('get_available_ml_jobs', () =>
|
const [jobs, legacyJobs] = await withApmSpan('get_available_ml_jobs', () =>
|
||||||
Promise.all([
|
Promise.all([
|
||||||
getAnomalyDetectionJobs(setup, context.logger),
|
getAnomalyDetectionJobs(setup, logger),
|
||||||
hasLegacyJobs(setup),
|
hasLegacyJobs(setup),
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
@ -47,7 +49,7 @@ export const anomalyDetectionJobsRoute = createRoute({
|
||||||
});
|
});
|
||||||
|
|
||||||
// create new ML anomaly detection jobs for each given environment
|
// create new ML anomaly detection jobs for each given environment
|
||||||
export const createAnomalyDetectionJobsRoute = createRoute({
|
const createAnomalyDetectionJobsRoute = createApmServerRoute({
|
||||||
endpoint: 'POST /api/apm/settings/anomaly-detection/jobs',
|
endpoint: 'POST /api/apm/settings/anomaly-detection/jobs',
|
||||||
options: {
|
options: {
|
||||||
tags: ['access:apm', 'access:apm_write', 'access:ml:canCreateJob'],
|
tags: ['access:apm', 'access:apm_write', 'access:ml:canCreateJob'],
|
||||||
|
@ -57,15 +59,17 @@ export const createAnomalyDetectionJobsRoute = createRoute({
|
||||||
environments: t.array(t.string),
|
environments: t.array(t.string),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const { environments } = context.params.body;
|
const { params, context, logger } = resources;
|
||||||
const setup = await setupRequest(context, request);
|
const { environments } = params.body;
|
||||||
|
|
||||||
|
const setup = await setupRequest(resources);
|
||||||
|
|
||||||
if (!isActivePlatinumLicense(context.licensing.license)) {
|
if (!isActivePlatinumLicense(context.licensing.license)) {
|
||||||
throw Boom.forbidden(ML_ERRORS.INVALID_LICENSE);
|
throw Boom.forbidden(ML_ERRORS.INVALID_LICENSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
await createAnomalyDetectionJobs(setup, environments, context.logger);
|
await createAnomalyDetectionJobs(setup, environments, logger);
|
||||||
|
|
||||||
notifyFeatureUsage({
|
notifyFeatureUsage({
|
||||||
licensingPlugin: context.licensing,
|
licensingPlugin: context.licensing,
|
||||||
|
@ -77,11 +81,11 @@ export const createAnomalyDetectionJobsRoute = createRoute({
|
||||||
});
|
});
|
||||||
|
|
||||||
// get all available environments to create anomaly detection jobs for
|
// get all available environments to create anomaly detection jobs for
|
||||||
export const anomalyDetectionEnvironmentsRoute = createRoute({
|
const anomalyDetectionEnvironmentsRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/settings/anomaly-detection/environments',
|
endpoint: 'GET /api/apm/settings/anomaly-detection/environments',
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
|
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
|
@ -96,3 +100,8 @@ export const anomalyDetectionEnvironmentsRoute = createRoute({
|
||||||
return { environments };
|
return { environments };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const anomalyDetectionRouteRepository = createApmServerRouteRepository()
|
||||||
|
.add(anomalyDetectionJobsRoute)
|
||||||
|
.add(createAnomalyDetectionJobsRoute)
|
||||||
|
.add(anomalyDetectionEnvironmentsRoute);
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as t from 'io-ts';
|
import * as t from 'io-ts';
|
||||||
import { createRoute } from '../create_route';
|
import { createApmServerRouteRepository } from '../create_apm_server_route_repository';
|
||||||
|
import { createApmServerRoute } from '../create_apm_server_route';
|
||||||
import {
|
import {
|
||||||
getApmIndices,
|
getApmIndices,
|
||||||
getApmIndexSettings,
|
getApmIndexSettings,
|
||||||
|
@ -14,29 +15,30 @@ import {
|
||||||
import { saveApmIndices } from '../../lib/settings/apm_indices/save_apm_indices';
|
import { saveApmIndices } from '../../lib/settings/apm_indices/save_apm_indices';
|
||||||
|
|
||||||
// get list of apm indices and values
|
// get list of apm indices and values
|
||||||
export const apmIndexSettingsRoute = createRoute({
|
const apmIndexSettingsRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/settings/apm-index-settings',
|
endpoint: 'GET /api/apm/settings/apm-index-settings',
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context }) => {
|
handler: async ({ config, context }) => {
|
||||||
const apmIndexSettings = await getApmIndexSettings({ context });
|
const apmIndexSettings = await getApmIndexSettings({ config, context });
|
||||||
return { apmIndexSettings };
|
return { apmIndexSettings };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// get apm indices configuration object
|
// get apm indices configuration object
|
||||||
export const apmIndicesRoute = createRoute({
|
const apmIndicesRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/settings/apm-indices',
|
endpoint: 'GET /api/apm/settings/apm-indices',
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context }) => {
|
handler: async (resources) => {
|
||||||
|
const { context, config } = resources;
|
||||||
return await getApmIndices({
|
return await getApmIndices({
|
||||||
savedObjectsClient: context.core.savedObjects.client,
|
savedObjectsClient: context.core.savedObjects.client,
|
||||||
config: context.config,
|
config,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// save ui indices
|
// save ui indices
|
||||||
export const saveApmIndicesRoute = createRoute({
|
const saveApmIndicesRoute = createApmServerRoute({
|
||||||
endpoint: 'POST /api/apm/settings/apm-indices/save',
|
endpoint: 'POST /api/apm/settings/apm-indices/save',
|
||||||
options: {
|
options: {
|
||||||
tags: ['access:apm', 'access:apm_write'],
|
tags: ['access:apm', 'access:apm_write'],
|
||||||
|
@ -53,9 +55,15 @@ export const saveApmIndicesRoute = createRoute({
|
||||||
/* eslint-enable @typescript-eslint/naming-convention */
|
/* eslint-enable @typescript-eslint/naming-convention */
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
handler: async ({ context }) => {
|
handler: async (resources) => {
|
||||||
const { body } = context.params;
|
const { params, context } = resources;
|
||||||
|
const { body } = params;
|
||||||
const savedObjectsClient = context.core.savedObjects.client;
|
const savedObjectsClient = context.core.savedObjects.client;
|
||||||
return await saveApmIndices(savedObjectsClient, body);
|
return await saveApmIndices(savedObjectsClient, body);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const apmIndicesRouteRepository = createApmServerRouteRepository()
|
||||||
|
.add(apmIndexSettingsRoute)
|
||||||
|
.add(apmIndicesRoute)
|
||||||
|
.add(saveApmIndicesRoute);
|
||||||
|
|
|
@ -21,35 +21,40 @@ import {
|
||||||
import { deleteCustomLink } from '../../lib/settings/custom_link/delete_custom_link';
|
import { deleteCustomLink } from '../../lib/settings/custom_link/delete_custom_link';
|
||||||
import { getTransaction } from '../../lib/settings/custom_link/get_transaction';
|
import { getTransaction } from '../../lib/settings/custom_link/get_transaction';
|
||||||
import { listCustomLinks } from '../../lib/settings/custom_link/list_custom_links';
|
import { listCustomLinks } from '../../lib/settings/custom_link/list_custom_links';
|
||||||
import { createRoute } from '../create_route';
|
import { createApmServerRoute } from '../create_apm_server_route';
|
||||||
|
import { createApmServerRouteRepository } from '../create_apm_server_route_repository';
|
||||||
|
|
||||||
export const customLinkTransactionRoute = createRoute({
|
const customLinkTransactionRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/settings/custom_links/transaction',
|
endpoint: 'GET /api/apm/settings/custom_links/transaction',
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
params: t.partial({
|
params: t.partial({
|
||||||
query: filterOptionsRt,
|
query: filterOptionsRt,
|
||||||
}),
|
}),
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { query } = context.params;
|
const { params } = resources;
|
||||||
|
const { query } = params;
|
||||||
// picks only the items listed in FILTER_OPTIONS
|
// picks only the items listed in FILTER_OPTIONS
|
||||||
const filters = pick(query, FILTER_OPTIONS);
|
const filters = pick(query, FILTER_OPTIONS);
|
||||||
return await getTransaction({ setup, filters });
|
return await getTransaction({ setup, filters });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const listCustomLinksRoute = createRoute({
|
const listCustomLinksRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/settings/custom_links',
|
endpoint: 'GET /api/apm/settings/custom_links',
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
params: t.partial({
|
params: t.partial({
|
||||||
query: filterOptionsRt,
|
query: filterOptionsRt,
|
||||||
}),
|
}),
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
|
const { context, params } = resources;
|
||||||
if (!isActiveGoldLicense(context.licensing.license)) {
|
if (!isActiveGoldLicense(context.licensing.license)) {
|
||||||
throw Boom.forbidden(INVALID_LICENSE);
|
throw Boom.forbidden(INVALID_LICENSE);
|
||||||
}
|
}
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { query } = context.params;
|
|
||||||
|
const { query } = params;
|
||||||
|
|
||||||
// picks only the items listed in FILTER_OPTIONS
|
// picks only the items listed in FILTER_OPTIONS
|
||||||
const filters = pick(query, FILTER_OPTIONS);
|
const filters = pick(query, FILTER_OPTIONS);
|
||||||
const customLinks = await listCustomLinks({ setup, filters });
|
const customLinks = await listCustomLinks({ setup, filters });
|
||||||
|
@ -57,29 +62,30 @@ export const listCustomLinksRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const createCustomLinkRoute = createRoute({
|
const createCustomLinkRoute = createApmServerRoute({
|
||||||
endpoint: 'POST /api/apm/settings/custom_links',
|
endpoint: 'POST /api/apm/settings/custom_links',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
body: payloadRt,
|
body: payloadRt,
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm', 'access:apm_write'] },
|
options: { tags: ['access:apm', 'access:apm_write'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
|
const { context, params } = resources;
|
||||||
if (!isActiveGoldLicense(context.licensing.license)) {
|
if (!isActiveGoldLicense(context.licensing.license)) {
|
||||||
throw Boom.forbidden(INVALID_LICENSE);
|
throw Boom.forbidden(INVALID_LICENSE);
|
||||||
}
|
}
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const customLink = context.params.body;
|
const customLink = params.body;
|
||||||
const res = await createOrUpdateCustomLink({ customLink, setup });
|
|
||||||
|
|
||||||
notifyFeatureUsage({
|
notifyFeatureUsage({
|
||||||
licensingPlugin: context.licensing,
|
licensingPlugin: context.licensing,
|
||||||
featureName: 'customLinks',
|
featureName: 'customLinks',
|
||||||
});
|
});
|
||||||
return res;
|
|
||||||
|
await createOrUpdateCustomLink({ customLink, setup });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const updateCustomLinkRoute = createRoute({
|
const updateCustomLinkRoute = createApmServerRoute({
|
||||||
endpoint: 'PUT /api/apm/settings/custom_links/{id}',
|
endpoint: 'PUT /api/apm/settings/custom_links/{id}',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({
|
path: t.type({
|
||||||
|
@ -90,23 +96,26 @@ export const updateCustomLinkRoute = createRoute({
|
||||||
options: {
|
options: {
|
||||||
tags: ['access:apm', 'access:apm_write'],
|
tags: ['access:apm', 'access:apm_write'],
|
||||||
},
|
},
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
|
const { params, context } = resources;
|
||||||
|
|
||||||
if (!isActiveGoldLicense(context.licensing.license)) {
|
if (!isActiveGoldLicense(context.licensing.license)) {
|
||||||
throw Boom.forbidden(INVALID_LICENSE);
|
throw Boom.forbidden(INVALID_LICENSE);
|
||||||
}
|
}
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { id } = context.params.path;
|
|
||||||
const customLink = context.params.body;
|
const { id } = params.path;
|
||||||
const res = await createOrUpdateCustomLink({
|
const customLink = params.body;
|
||||||
|
|
||||||
|
await createOrUpdateCustomLink({
|
||||||
customLinkId: id,
|
customLinkId: id,
|
||||||
customLink,
|
customLink,
|
||||||
setup,
|
setup,
|
||||||
});
|
});
|
||||||
return res;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const deleteCustomLinkRoute = createRoute({
|
const deleteCustomLinkRoute = createApmServerRoute({
|
||||||
endpoint: 'DELETE /api/apm/settings/custom_links/{id}',
|
endpoint: 'DELETE /api/apm/settings/custom_links/{id}',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({
|
path: t.type({
|
||||||
|
@ -116,12 +125,14 @@ export const deleteCustomLinkRoute = createRoute({
|
||||||
options: {
|
options: {
|
||||||
tags: ['access:apm', 'access:apm_write'],
|
tags: ['access:apm', 'access:apm_write'],
|
||||||
},
|
},
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
|
const { context, params } = resources;
|
||||||
|
|
||||||
if (!isActiveGoldLicense(context.licensing.license)) {
|
if (!isActiveGoldLicense(context.licensing.license)) {
|
||||||
throw Boom.forbidden(INVALID_LICENSE);
|
throw Boom.forbidden(INVALID_LICENSE);
|
||||||
}
|
}
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { id } = context.params.path;
|
const { id } = params.path;
|
||||||
const res = await deleteCustomLink({
|
const res = await deleteCustomLink({
|
||||||
customLinkId: id,
|
customLinkId: id,
|
||||||
setup,
|
setup,
|
||||||
|
@ -129,3 +140,10 @@ export const deleteCustomLinkRoute = createRoute({
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const customLinkRouteRepository = createApmServerRouteRepository()
|
||||||
|
.add(customLinkTransactionRoute)
|
||||||
|
.add(listCustomLinksRoute)
|
||||||
|
.add(createCustomLinkRoute)
|
||||||
|
.add(updateCustomLinkRoute)
|
||||||
|
.add(deleteCustomLinkRoute);
|
||||||
|
|
|
@ -9,20 +9,22 @@ import * as t from 'io-ts';
|
||||||
import { setupRequest } from '../lib/helpers/setup_request';
|
import { setupRequest } from '../lib/helpers/setup_request';
|
||||||
import { getTrace } from '../lib/traces/get_trace';
|
import { getTrace } from '../lib/traces/get_trace';
|
||||||
import { getTransactionGroupList } from '../lib/transaction_groups';
|
import { getTransactionGroupList } from '../lib/transaction_groups';
|
||||||
import { createRoute } from './create_route';
|
import { createApmServerRoute } from './create_apm_server_route';
|
||||||
import { environmentRt, kueryRt, rangeRt } from './default_api_types';
|
import { environmentRt, kueryRt, rangeRt } from './default_api_types';
|
||||||
import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions';
|
import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions';
|
||||||
import { getRootTransactionByTraceId } from '../lib/transactions/get_transaction_by_trace';
|
import { getRootTransactionByTraceId } from '../lib/transactions/get_transaction_by_trace';
|
||||||
|
import { createApmServerRouteRepository } from './create_apm_server_route_repository';
|
||||||
|
|
||||||
export const tracesRoute = createRoute({
|
const tracesRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/traces',
|
endpoint: 'GET /api/apm/traces',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
query: t.intersection([environmentRt, kueryRt, rangeRt]),
|
query: t.intersection([environmentRt, kueryRt, rangeRt]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { environment, kuery } = context.params.query;
|
const { params } = resources;
|
||||||
|
const { environment, kuery } = params.query;
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
);
|
);
|
||||||
|
@ -34,7 +36,7 @@ export const tracesRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const tracesByIdRoute = createRoute({
|
const tracesByIdRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/traces/{traceId}',
|
endpoint: 'GET /api/apm/traces/{traceId}',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({
|
path: t.type({
|
||||||
|
@ -43,13 +45,16 @@ export const tracesByIdRoute = createRoute({
|
||||||
query: rangeRt,
|
query: rangeRt,
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
return getTrace(context.params.path.traceId, setup);
|
const { params } = resources;
|
||||||
|
|
||||||
|
const { traceId } = params.path;
|
||||||
|
return getTrace(traceId, setup);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const rootTransactionByTraceIdRoute = createRoute({
|
const rootTransactionByTraceIdRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/traces/{traceId}/root_transaction',
|
endpoint: 'GET /api/apm/traces/{traceId}/root_transaction',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({
|
path: t.type({
|
||||||
|
@ -57,9 +62,15 @@ export const rootTransactionByTraceIdRoute = createRoute({
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const { traceId } = context.params.path;
|
const { params } = resources;
|
||||||
const setup = await setupRequest(context, request);
|
const { traceId } = params.path;
|
||||||
|
const setup = await setupRequest(resources);
|
||||||
return getRootTransactionByTraceId(traceId, setup);
|
return getRootTransactionByTraceId(traceId, setup);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const traceRouteRepository = createApmServerRouteRepository()
|
||||||
|
.add(tracesByIdRoute)
|
||||||
|
.add(tracesRoute)
|
||||||
|
.add(rootTransactionByTraceIdRoute);
|
||||||
|
|
|
@ -5,12 +5,12 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { jsonRt } from '@kbn/io-ts-utils';
|
||||||
import * as t from 'io-ts';
|
import * as t from 'io-ts';
|
||||||
import {
|
import {
|
||||||
LatencyAggregationType,
|
LatencyAggregationType,
|
||||||
latencyAggregationTypeRt,
|
latencyAggregationTypeRt,
|
||||||
} from '../../common/latency_aggregation_types';
|
} from '../../common/latency_aggregation_types';
|
||||||
import { jsonRt } from '../../common/runtime_types/json_rt';
|
|
||||||
import { toNumberRt } from '../../common/runtime_types/to_number_rt';
|
import { toNumberRt } from '../../common/runtime_types/to_number_rt';
|
||||||
import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions';
|
import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions';
|
||||||
import { setupRequest } from '../lib/helpers/setup_request';
|
import { setupRequest } from '../lib/helpers/setup_request';
|
||||||
|
@ -23,7 +23,8 @@ import { getLatencyPeriods } from '../lib/transactions/get_latency_charts';
|
||||||
import { getThroughputCharts } from '../lib/transactions/get_throughput_charts';
|
import { getThroughputCharts } from '../lib/transactions/get_throughput_charts';
|
||||||
import { getTransactionGroupList } from '../lib/transaction_groups';
|
import { getTransactionGroupList } from '../lib/transaction_groups';
|
||||||
import { getErrorRatePeriods } from '../lib/transaction_groups/get_error_rate';
|
import { getErrorRatePeriods } from '../lib/transaction_groups/get_error_rate';
|
||||||
import { createRoute } from './create_route';
|
import { createApmServerRoute } from './create_apm_server_route';
|
||||||
|
import { createApmServerRouteRepository } from './create_apm_server_route_repository';
|
||||||
import {
|
import {
|
||||||
comparisonRangeRt,
|
comparisonRangeRt,
|
||||||
environmentRt,
|
environmentRt,
|
||||||
|
@ -35,7 +36,7 @@ import {
|
||||||
* Returns a list of transactions grouped by name
|
* Returns a list of transactions grouped by name
|
||||||
* //TODO: delete this once we moved away from the old table in the transaction overview page. It should be replaced by /transactions/groups/primary_statistics/
|
* //TODO: delete this once we moved away from the old table in the transaction overview page. It should be replaced by /transactions/groups/primary_statistics/
|
||||||
*/
|
*/
|
||||||
export const transactionGroupsRoute = createRoute({
|
const transactionGroupsRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/services/{serviceName}/transactions/groups',
|
endpoint: 'GET /api/apm/services/{serviceName}/transactions/groups',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({
|
path: t.type({
|
||||||
|
@ -49,10 +50,11 @@ export const transactionGroupsRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { serviceName } = context.params.path;
|
const { params } = resources;
|
||||||
const { environment, kuery, transactionType } = context.params.query;
|
const { serviceName } = params.path;
|
||||||
|
const { environment, kuery, transactionType } = params.query;
|
||||||
|
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
|
@ -72,7 +74,7 @@ export const transactionGroupsRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const transactionGroupsPrimaryStatisticsRoute = createRoute({
|
const transactionGroupsPrimaryStatisticsRoute = createApmServerRoute({
|
||||||
endpoint:
|
endpoint:
|
||||||
'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics',
|
'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
|
@ -90,8 +92,9 @@ export const transactionGroupsPrimaryStatisticsRoute = createRoute({
|
||||||
options: {
|
options: {
|
||||||
tags: ['access:apm'],
|
tags: ['access:apm'],
|
||||||
},
|
},
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const { params } = resources;
|
||||||
|
const setup = await setupRequest(resources);
|
||||||
|
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
|
@ -100,7 +103,7 @@ export const transactionGroupsPrimaryStatisticsRoute = createRoute({
|
||||||
const {
|
const {
|
||||||
path: { serviceName },
|
path: { serviceName },
|
||||||
query: { environment, kuery, latencyAggregationType, transactionType },
|
query: { environment, kuery, latencyAggregationType, transactionType },
|
||||||
} = context.params;
|
} = params;
|
||||||
|
|
||||||
return getServiceTransactionGroups({
|
return getServiceTransactionGroups({
|
||||||
environment,
|
environment,
|
||||||
|
@ -109,12 +112,12 @@ export const transactionGroupsPrimaryStatisticsRoute = createRoute({
|
||||||
serviceName,
|
serviceName,
|
||||||
searchAggregatedTransactions,
|
searchAggregatedTransactions,
|
||||||
transactionType,
|
transactionType,
|
||||||
latencyAggregationType: latencyAggregationType as LatencyAggregationType,
|
latencyAggregationType,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const transactionGroupsComparisonStatisticsRoute = createRoute({
|
const transactionGroupsComparisonStatisticsRoute = createApmServerRoute({
|
||||||
endpoint:
|
endpoint:
|
||||||
'GET /api/apm/services/{serviceName}/transactions/groups/comparison_statistics',
|
'GET /api/apm/services/{serviceName}/transactions/groups/comparison_statistics',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
|
@ -135,13 +138,15 @@ export const transactionGroupsComparisonStatisticsRoute = createRoute({
|
||||||
options: {
|
options: {
|
||||||
tags: ['access:apm'],
|
tags: ['access:apm'],
|
||||||
},
|
},
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
|
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { params } = resources;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
path: { serviceName },
|
path: { serviceName },
|
||||||
query: {
|
query: {
|
||||||
|
@ -154,7 +159,7 @@ export const transactionGroupsComparisonStatisticsRoute = createRoute({
|
||||||
comparisonStart,
|
comparisonStart,
|
||||||
comparisonEnd,
|
comparisonEnd,
|
||||||
},
|
},
|
||||||
} = context.params;
|
} = params;
|
||||||
|
|
||||||
return await getServiceTransactionGroupComparisonStatisticsPeriods({
|
return await getServiceTransactionGroupComparisonStatisticsPeriods({
|
||||||
environment,
|
environment,
|
||||||
|
@ -165,14 +170,14 @@ export const transactionGroupsComparisonStatisticsRoute = createRoute({
|
||||||
searchAggregatedTransactions,
|
searchAggregatedTransactions,
|
||||||
transactionType,
|
transactionType,
|
||||||
numBuckets,
|
numBuckets,
|
||||||
latencyAggregationType: latencyAggregationType as LatencyAggregationType,
|
latencyAggregationType,
|
||||||
comparisonStart,
|
comparisonStart,
|
||||||
comparisonEnd,
|
comparisonEnd,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const transactionLatencyChartsRoute = createRoute({
|
const transactionLatencyChartsRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/services/{serviceName}/transactions/charts/latency',
|
endpoint: 'GET /api/apm/services/{serviceName}/transactions/charts/latency',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({
|
path: t.type({
|
||||||
|
@ -188,10 +193,11 @@ export const transactionLatencyChartsRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const logger = context.logger;
|
const { params, logger } = resources;
|
||||||
const { serviceName } = context.params.path;
|
|
||||||
|
const { serviceName } = params.path;
|
||||||
const {
|
const {
|
||||||
environment,
|
environment,
|
||||||
kuery,
|
kuery,
|
||||||
|
@ -200,7 +206,7 @@ export const transactionLatencyChartsRoute = createRoute({
|
||||||
latencyAggregationType,
|
latencyAggregationType,
|
||||||
comparisonStart,
|
comparisonStart,
|
||||||
comparisonEnd,
|
comparisonEnd,
|
||||||
} = context.params.query;
|
} = params.query;
|
||||||
|
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
|
@ -242,7 +248,7 @@ export const transactionLatencyChartsRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const transactionThroughputChartsRoute = createRoute({
|
const transactionThroughputChartsRoute = createApmServerRoute({
|
||||||
endpoint:
|
endpoint:
|
||||||
'GET /api/apm/services/{serviceName}/transactions/charts/throughput',
|
'GET /api/apm/services/{serviceName}/transactions/charts/throughput',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
|
@ -258,15 +264,17 @@ export const transactionThroughputChartsRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { serviceName } = context.params.path;
|
const { params } = resources;
|
||||||
|
|
||||||
|
const { serviceName } = params.path;
|
||||||
const {
|
const {
|
||||||
environment,
|
environment,
|
||||||
kuery,
|
kuery,
|
||||||
transactionType,
|
transactionType,
|
||||||
transactionName,
|
transactionName,
|
||||||
} = context.params.query;
|
} = params.query;
|
||||||
|
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
|
@ -284,7 +292,7 @@ export const transactionThroughputChartsRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const transactionChartsDistributionRoute = createRoute({
|
const transactionChartsDistributionRoute = createApmServerRoute({
|
||||||
endpoint:
|
endpoint:
|
||||||
'GET /api/apm/services/{serviceName}/transactions/charts/distribution',
|
'GET /api/apm/services/{serviceName}/transactions/charts/distribution',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
|
@ -306,9 +314,10 @@ export const transactionChartsDistributionRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { serviceName } = context.params.path;
|
const { params } = resources;
|
||||||
|
const { serviceName } = params.path;
|
||||||
const {
|
const {
|
||||||
environment,
|
environment,
|
||||||
kuery,
|
kuery,
|
||||||
|
@ -316,7 +325,7 @@ export const transactionChartsDistributionRoute = createRoute({
|
||||||
transactionName,
|
transactionName,
|
||||||
transactionId = '',
|
transactionId = '',
|
||||||
traceId = '',
|
traceId = '',
|
||||||
} = context.params.query;
|
} = params.query;
|
||||||
|
|
||||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||||
setup
|
setup
|
||||||
|
@ -336,7 +345,7 @@ export const transactionChartsDistributionRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const transactionChartsBreakdownRoute = createRoute({
|
const transactionChartsBreakdownRoute = createApmServerRoute({
|
||||||
endpoint: 'GET /api/apm/services/{serviceName}/transaction/charts/breakdown',
|
endpoint: 'GET /api/apm/services/{serviceName}/transaction/charts/breakdown',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
path: t.type({
|
path: t.type({
|
||||||
|
@ -351,15 +360,17 @@ export const transactionChartsBreakdownRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { serviceName } = context.params.path;
|
const { params } = resources;
|
||||||
|
|
||||||
|
const { serviceName } = params.path;
|
||||||
const {
|
const {
|
||||||
environment,
|
environment,
|
||||||
kuery,
|
kuery,
|
||||||
transactionName,
|
transactionName,
|
||||||
transactionType,
|
transactionType,
|
||||||
} = context.params.query;
|
} = params.query;
|
||||||
|
|
||||||
return getTransactionBreakdown({
|
return getTransactionBreakdown({
|
||||||
environment,
|
environment,
|
||||||
|
@ -372,7 +383,7 @@ export const transactionChartsBreakdownRoute = createRoute({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const transactionChartsErrorRateRoute = createRoute({
|
const transactionChartsErrorRateRoute = createApmServerRoute({
|
||||||
endpoint:
|
endpoint:
|
||||||
'GET /api/apm/services/{serviceName}/transactions/charts/error_rate',
|
'GET /api/apm/services/{serviceName}/transactions/charts/error_rate',
|
||||||
params: t.type({
|
params: t.type({
|
||||||
|
@ -386,9 +397,10 @@ export const transactionChartsErrorRateRoute = createRoute({
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
options: { tags: ['access:apm'] },
|
options: { tags: ['access:apm'] },
|
||||||
handler: async ({ context, request }) => {
|
handler: async (resources) => {
|
||||||
const setup = await setupRequest(context, request);
|
const setup = await setupRequest(resources);
|
||||||
const { params } = context;
|
|
||||||
|
const { params } = resources;
|
||||||
const { serviceName } = params.path;
|
const { serviceName } = params.path;
|
||||||
const {
|
const {
|
||||||
environment,
|
environment,
|
||||||
|
@ -416,3 +428,13 @@ export const transactionChartsErrorRateRoute = createRoute({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const transactionRouteRepository = createApmServerRouteRepository()
|
||||||
|
.add(transactionGroupsRoute)
|
||||||
|
.add(transactionGroupsPrimaryStatisticsRoute)
|
||||||
|
.add(transactionGroupsComparisonStatisticsRoute)
|
||||||
|
.add(transactionLatencyChartsRoute)
|
||||||
|
.add(transactionThroughputChartsRoute)
|
||||||
|
.add(transactionChartsDistributionRoute)
|
||||||
|
.add(transactionChartsBreakdownRoute)
|
||||||
|
.add(transactionChartsErrorRateRoute);
|
||||||
|
|
|
@ -5,27 +5,19 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import t, { Encode, Encoder } from 'io-ts';
|
|
||||||
import {
|
import {
|
||||||
CoreSetup,
|
CoreSetup,
|
||||||
KibanaRequest,
|
|
||||||
RequestHandlerContext,
|
RequestHandlerContext,
|
||||||
Logger,
|
Logger,
|
||||||
|
KibanaRequest,
|
||||||
|
CoreStart,
|
||||||
} from 'src/core/server';
|
} from 'src/core/server';
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { RequiredKeys, DeepPartial } from 'utility-types';
|
|
||||||
import { SpacesPluginStart } from '../../../spaces/server';
|
|
||||||
import { ObservabilityPluginSetup } from '../../../observability/server';
|
|
||||||
import { LicensingApiRequestHandlerContext } from '../../../licensing/server';
|
import { LicensingApiRequestHandlerContext } from '../../../licensing/server';
|
||||||
import { SecurityPluginSetup } from '../../../security/server';
|
|
||||||
import { MlPluginSetup } from '../../../ml/server';
|
|
||||||
import { FetchOptions } from '../../common/fetch_options';
|
|
||||||
import { APMConfig } from '..';
|
import { APMConfig } from '..';
|
||||||
|
import { APMPluginDependencies } from '../types';
|
||||||
|
|
||||||
export type HandlerReturn = Record<string, any>;
|
export interface ApmPluginRequestHandlerContext extends RequestHandlerContext {
|
||||||
|
licensing: LicensingApiRequestHandlerContext;
|
||||||
interface InspectQueryParam {
|
|
||||||
query: { _inspect: boolean };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type InspectResponse = Array<{
|
export type InspectResponse = Array<{
|
||||||
|
@ -36,141 +28,53 @@ export type InspectResponse = Array<{
|
||||||
esError: Error;
|
esError: Error;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export interface RouteParams {
|
export interface APMRouteCreateOptions {
|
||||||
path?: Record<string, unknown>;
|
options: {
|
||||||
query?: Record<string, unknown>;
|
tags: Array<
|
||||||
body?: any;
|
| 'access:apm'
|
||||||
|
| 'access:apm_write'
|
||||||
|
| 'access:ml:canGetJobs'
|
||||||
|
| 'access:ml:canCreateJob'
|
||||||
|
>;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type WithoutIncompatibleMethods<T extends t.Any> = Omit<
|
export interface APMRouteHandlerResources {
|
||||||
T,
|
|
||||||
'encode' | 'asEncoder'
|
|
||||||
> & { encode: Encode<any, any>; asEncoder: () => Encoder<any, any> };
|
|
||||||
|
|
||||||
export type RouteParamsRT = WithoutIncompatibleMethods<t.Type<RouteParams>>;
|
|
||||||
|
|
||||||
export type RouteHandler<
|
|
||||||
TParamsRT extends RouteParamsRT | undefined,
|
|
||||||
TReturn extends HandlerReturn
|
|
||||||
> = (kibanaContext: {
|
|
||||||
context: APMRequestHandlerContext<
|
|
||||||
(TParamsRT extends RouteParamsRT ? t.TypeOf<TParamsRT> : {}) &
|
|
||||||
InspectQueryParam
|
|
||||||
>;
|
|
||||||
request: KibanaRequest;
|
request: KibanaRequest;
|
||||||
}) => Promise<TReturn extends any[] ? never : TReturn>;
|
context: ApmPluginRequestHandlerContext;
|
||||||
|
params: {
|
||||||
interface RouteOptions {
|
query: {
|
||||||
tags: Array<
|
_inspect: boolean;
|
||||||
| 'access:apm'
|
};
|
||||||
| 'access:apm_write'
|
};
|
||||||
| 'access:ml:canGetJobs'
|
|
||||||
| 'access:ml:canCreateJob'
|
|
||||||
>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Route<
|
|
||||||
TEndpoint extends string,
|
|
||||||
TRouteParamsRT extends RouteParamsRT | undefined,
|
|
||||||
TReturn extends HandlerReturn
|
|
||||||
> {
|
|
||||||
endpoint: TEndpoint;
|
|
||||||
options: RouteOptions;
|
|
||||||
params?: TRouteParamsRT;
|
|
||||||
handler: RouteHandler<TRouteParamsRT, TReturn>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export interface ApmPluginRequestHandlerContext extends RequestHandlerContext {
|
|
||||||
licensing: LicensingApiRequestHandlerContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type APMRequestHandlerContext<
|
|
||||||
TRouteParams = {}
|
|
||||||
> = ApmPluginRequestHandlerContext & {
|
|
||||||
params: TRouteParams & InspectQueryParam;
|
|
||||||
config: APMConfig;
|
config: APMConfig;
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
|
core: {
|
||||||
|
setup: CoreSetup;
|
||||||
|
start: () => Promise<CoreStart>;
|
||||||
|
};
|
||||||
plugins: {
|
plugins: {
|
||||||
spaces?: SpacesPluginStart;
|
[key in keyof APMPluginDependencies]: {
|
||||||
observability?: ObservabilityPluginSetup;
|
setup: Required<APMPluginDependencies>[key]['setup'];
|
||||||
security?: SecurityPluginSetup;
|
start: () => Promise<Required<APMPluginDependencies>[key]['start']>;
|
||||||
ml?: MlPluginSetup;
|
};
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface RouteState {
|
|
||||||
[endpoint: string]: {
|
|
||||||
params?: RouteParams;
|
|
||||||
ret: any;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServerAPI<TRouteState extends RouteState> {
|
// export type Client<
|
||||||
_S: TRouteState;
|
// TRouteState,
|
||||||
add<
|
// TOptions extends { abortable: boolean } = { abortable: true }
|
||||||
TEndpoint extends string,
|
// > = <TEndpoint extends keyof TRouteState & string>(
|
||||||
TReturn extends HandlerReturn,
|
// options: Omit<
|
||||||
TRouteParamsRT extends RouteParamsRT | undefined = undefined
|
// FetchOptions,
|
||||||
>(
|
// 'query' | 'body' | 'pathname' | 'method' | 'signal'
|
||||||
route:
|
// > & {
|
||||||
| Route<TEndpoint, TRouteParamsRT, TReturn>
|
// forceCache?: boolean;
|
||||||
| ((core: CoreSetup) => Route<TEndpoint, TRouteParamsRT, TReturn>)
|
// endpoint: TEndpoint;
|
||||||
): ServerAPI<
|
// } & MaybeParams<TRouteState, TEndpoint> &
|
||||||
TRouteState &
|
// (TOptions extends { abortable: true } ? { signal: AbortSignal | null } : {})
|
||||||
{
|
// ) => Promise<
|
||||||
[key in TEndpoint]: {
|
// TRouteState[TEndpoint] extends { ret: any }
|
||||||
params: TRouteParamsRT;
|
// ? TRouteState[TEndpoint]['ret']
|
||||||
ret: TReturn & { _inspect?: InspectResponse };
|
// : unknown
|
||||||
};
|
// >;
|
||||||
}
|
|
||||||
>;
|
|
||||||
init: (
|
|
||||||
core: CoreSetup,
|
|
||||||
context: {
|
|
||||||
config$: Observable<APMConfig>;
|
|
||||||
logger: Logger;
|
|
||||||
plugins: {
|
|
||||||
observability?: ObservabilityPluginSetup;
|
|
||||||
security?: SecurityPluginSetup;
|
|
||||||
ml?: MlPluginSetup;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
type MaybeOptional<T extends { params: Record<string, any> }> = RequiredKeys<
|
|
||||||
T['params']
|
|
||||||
> extends never
|
|
||||||
? { 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<TRouteState[TEndpoint]['params']> &
|
|
||||||
DeepPartial<InspectQueryParam>;
|
|
||||||
}>
|
|
||||||
: {};
|
|
||||||
|
|
||||||
export type Client<
|
|
||||||
TRouteState,
|
|
||||||
TOptions extends { abortable: boolean } = { abortable: true }
|
|
||||||
> = <TEndpoint extends keyof TRouteState & string>(
|
|
||||||
options: Omit<
|
|
||||||
FetchOptions,
|
|
||||||
'query' | 'body' | 'pathname' | 'method' | 'signal'
|
|
||||||
> & {
|
|
||||||
forceCache?: boolean;
|
|
||||||
endpoint: TEndpoint;
|
|
||||||
} & MaybeParams<TRouteState, TEndpoint> &
|
|
||||||
(TOptions extends { abortable: true } ? { signal: AbortSignal | null } : {})
|
|
||||||
) => Promise<
|
|
||||||
TRouteState[TEndpoint] extends { ret: any }
|
|
||||||
? TRouteState[TEndpoint]['ret']
|
|
||||||
: unknown
|
|
||||||
>;
|
|
||||||
|
|
164
x-pack/plugins/apm/server/types.ts
Normal file
164
x-pack/plugins/apm/server/types.ts
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
/*
|
||||||
|
* 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 { ValuesType } from 'utility-types';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { CoreSetup, CoreStart, KibanaRequest } from 'kibana/server';
|
||||||
|
import {
|
||||||
|
PluginSetup as DataPluginSetup,
|
||||||
|
PluginStart as DataPluginStart,
|
||||||
|
} from '../../../../src/plugins/data/server';
|
||||||
|
import { SpacesPluginSetup, SpacesPluginStart } from '../../spaces/server';
|
||||||
|
import { APMOSSPluginSetup } from '../../../../src/plugins/apm_oss/server';
|
||||||
|
import {
|
||||||
|
HomeServerPluginSetup,
|
||||||
|
HomeServerPluginStart,
|
||||||
|
} from '../../../../src/plugins/home/server';
|
||||||
|
import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server';
|
||||||
|
import { ActionsPlugin } from '../../actions/server';
|
||||||
|
import { AlertingPlugin } from '../../alerting/server';
|
||||||
|
import { CloudSetup } from '../../cloud/server';
|
||||||
|
import {
|
||||||
|
PluginSetupContract as FeaturesPluginSetup,
|
||||||
|
PluginStartContract as FeaturesPluginStart,
|
||||||
|
} from '../../features/server';
|
||||||
|
import {
|
||||||
|
LicensingPluginSetup,
|
||||||
|
LicensingPluginStart,
|
||||||
|
} from '../../licensing/server';
|
||||||
|
import { MlPluginSetup, MlPluginStart } from '../../ml/server';
|
||||||
|
import { ObservabilityPluginSetup } from '../../observability/server';
|
||||||
|
import {
|
||||||
|
SecurityPluginSetup,
|
||||||
|
SecurityPluginStart,
|
||||||
|
} from '../../security/server';
|
||||||
|
import {
|
||||||
|
TaskManagerSetupContract,
|
||||||
|
TaskManagerStartContract,
|
||||||
|
} from '../../task_manager/server';
|
||||||
|
import { APMConfig } from '.';
|
||||||
|
import { getApmIndices } from './lib/settings/apm_indices/get_apm_indices';
|
||||||
|
import { createApmEventClient } from './lib/helpers/create_es_client/create_apm_event_client';
|
||||||
|
import { ApmPluginRequestHandlerContext } from './routes/typings';
|
||||||
|
|
||||||
|
export interface APMPluginSetup {
|
||||||
|
config$: Observable<APMConfig>;
|
||||||
|
getApmIndices: () => ReturnType<typeof getApmIndices>;
|
||||||
|
createApmEventClient: (params: {
|
||||||
|
debug?: boolean;
|
||||||
|
request: KibanaRequest;
|
||||||
|
context: ApmPluginRequestHandlerContext;
|
||||||
|
}) => Promise<ReturnType<typeof createApmEventClient>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DependencyMap {
|
||||||
|
core: {
|
||||||
|
setup: CoreSetup;
|
||||||
|
start: CoreStart;
|
||||||
|
};
|
||||||
|
spaces: {
|
||||||
|
setup: SpacesPluginSetup;
|
||||||
|
start: SpacesPluginStart;
|
||||||
|
};
|
||||||
|
apmOss: {
|
||||||
|
setup: APMOSSPluginSetup;
|
||||||
|
start: undefined;
|
||||||
|
};
|
||||||
|
home: {
|
||||||
|
setup: HomeServerPluginSetup;
|
||||||
|
start: HomeServerPluginStart;
|
||||||
|
};
|
||||||
|
licensing: {
|
||||||
|
setup: LicensingPluginSetup;
|
||||||
|
start: LicensingPluginStart;
|
||||||
|
};
|
||||||
|
cloud: {
|
||||||
|
setup: CloudSetup;
|
||||||
|
start: undefined;
|
||||||
|
};
|
||||||
|
usageCollection: {
|
||||||
|
setup: UsageCollectionSetup;
|
||||||
|
start: undefined;
|
||||||
|
};
|
||||||
|
taskManager: {
|
||||||
|
setup: TaskManagerSetupContract;
|
||||||
|
start: TaskManagerStartContract;
|
||||||
|
};
|
||||||
|
alerting: {
|
||||||
|
setup: AlertingPlugin['setup'];
|
||||||
|
start: AlertingPlugin['start'];
|
||||||
|
};
|
||||||
|
actions: {
|
||||||
|
setup: ActionsPlugin['setup'];
|
||||||
|
start: ActionsPlugin['start'];
|
||||||
|
};
|
||||||
|
observability: {
|
||||||
|
setup: ObservabilityPluginSetup;
|
||||||
|
start: undefined;
|
||||||
|
};
|
||||||
|
features: {
|
||||||
|
setup: FeaturesPluginSetup;
|
||||||
|
start: FeaturesPluginStart;
|
||||||
|
};
|
||||||
|
security: {
|
||||||
|
setup: SecurityPluginSetup;
|
||||||
|
start: SecurityPluginStart;
|
||||||
|
};
|
||||||
|
ml: {
|
||||||
|
setup: MlPluginSetup;
|
||||||
|
start: MlPluginStart;
|
||||||
|
};
|
||||||
|
data: {
|
||||||
|
setup: DataPluginSetup;
|
||||||
|
start: DataPluginStart;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const requiredDependencies = [
|
||||||
|
'features',
|
||||||
|
'apmOss',
|
||||||
|
'data',
|
||||||
|
'licensing',
|
||||||
|
'triggersActionsUi',
|
||||||
|
'embeddable',
|
||||||
|
'infra',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const optionalDependencies = [
|
||||||
|
'spaces',
|
||||||
|
'cloud',
|
||||||
|
'usageCollection',
|
||||||
|
'taskManager',
|
||||||
|
'actions',
|
||||||
|
'alerting',
|
||||||
|
'observability',
|
||||||
|
'security',
|
||||||
|
'ml',
|
||||||
|
'home',
|
||||||
|
'maps',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
type RequiredDependencies = Pick<
|
||||||
|
DependencyMap,
|
||||||
|
ValuesType<typeof requiredDependencies> & keyof DependencyMap
|
||||||
|
>;
|
||||||
|
|
||||||
|
type OptionalDependencies = Partial<
|
||||||
|
Pick<
|
||||||
|
DependencyMap,
|
||||||
|
ValuesType<typeof optionalDependencies> & keyof DependencyMap
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type APMPluginDependencies = RequiredDependencies & OptionalDependencies;
|
||||||
|
|
||||||
|
export type APMPluginSetupDependencies = {
|
||||||
|
[key in keyof APMPluginDependencies]: Required<APMPluginDependencies>[key]['setup'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type APMPluginStartDependencies = {
|
||||||
|
[key in keyof APMPluginDependencies]: Required<APMPluginDependencies>[key]['start'];
|
||||||
|
};
|
|
@ -8,24 +8,25 @@
|
||||||
import { format } from 'url';
|
import { format } from 'url';
|
||||||
import supertest from 'supertest';
|
import supertest from 'supertest';
|
||||||
import request from 'superagent';
|
import request from 'superagent';
|
||||||
import { MaybeParams } from '../../../plugins/apm/server/routes/typings';
|
|
||||||
import { parseEndpoint } from '../../../plugins/apm/common/apm_api/parse_endpoint';
|
import { parseEndpoint } from '../../../plugins/apm/common/apm_api/parse_endpoint';
|
||||||
import { APMAPI } from '../../../plugins/apm/server/routes/create_apm_api';
|
import type {
|
||||||
import type { APIReturnType } from '../../../plugins/apm/public/services/rest/createCallApmApi';
|
APIReturnType,
|
||||||
|
APIEndpoint,
|
||||||
|
APIClientRequestParamsOf,
|
||||||
|
} from '../../../plugins/apm/public/services/rest/createCallApmApi';
|
||||||
|
|
||||||
export function createApmApiSupertest(st: supertest.SuperTest<supertest.Test>) {
|
export function createApmApiSupertest(st: supertest.SuperTest<supertest.Test>) {
|
||||||
return async <TPath extends keyof APMAPI['_S']>(
|
return async <TEndpoint extends APIEndpoint>(
|
||||||
options: {
|
options: {
|
||||||
endpoint: TPath;
|
endpoint: TEndpoint;
|
||||||
} & MaybeParams<APMAPI['_S'], TPath>
|
} & APIClientRequestParamsOf<TEndpoint> & { params?: { query?: { _inspect?: boolean } } }
|
||||||
): Promise<{
|
): Promise<{
|
||||||
status: number;
|
status: number;
|
||||||
body: APIReturnType<TPath>;
|
body: APIReturnType<TEndpoint>;
|
||||||
}> => {
|
}> => {
|
||||||
const { endpoint } = options;
|
const { endpoint } = options;
|
||||||
|
|
||||||
// @ts-expect-error
|
const params = 'params' in options ? (options.params as Record<string, any>) : {};
|
||||||
const params = 'params' in options ? options.params : {};
|
|
||||||
|
|
||||||
const { method, pathname } = parseEndpoint(endpoint, params?.path);
|
const { method, pathname } = parseEndpoint(endpoint, params?.path);
|
||||||
const url = format({ pathname, query: params?.query });
|
const url = format({ pathname, query: params?.query });
|
||||||
|
|
|
@ -81,7 +81,6 @@ export default function customLinksTests({ getService }: FtrProviderContext) {
|
||||||
it('for agent configs', async () => {
|
it('for agent configs', async () => {
|
||||||
const { status, body } = await supertestRead({
|
const { status, body } = await supertestRead({
|
||||||
endpoint: 'GET /api/apm/settings/agent-configuration',
|
endpoint: 'GET /api/apm/settings/agent-configuration',
|
||||||
// @ts-expect-error
|
|
||||||
params: {
|
params: {
|
||||||
query: {
|
query: {
|
||||||
_inspect: true,
|
_inspect: true,
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||||
import archives from '../../common/fixtures/es_archiver/archives_metadata';
|
import archives from '../../common/fixtures/es_archiver/archives_metadata';
|
||||||
import { registry } from '../../common/registry';
|
import { registry } from '../../common/registry';
|
||||||
import { createApmApiSupertest } from '../../common/apm_api_supertest';
|
import { createApmApiSupertest } from '../../common/apm_api_supertest';
|
||||||
|
import { LatencyAggregationType } from '../../../../plugins/apm/common/latency_aggregation_types';
|
||||||
|
|
||||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||||
const apmApiSupertest = createApmApiSupertest(getService('supertest'));
|
const apmApiSupertest = createApmApiSupertest(getService('supertest'));
|
||||||
|
@ -31,7 +32,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
||||||
params: {
|
params: {
|
||||||
path: { serviceName: 'opbeans-java' },
|
path: { serviceName: 'opbeans-java' },
|
||||||
query: {
|
query: {
|
||||||
latencyAggregationType: 'avg',
|
latencyAggregationType: LatencyAggregationType.avg,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
transactionType: 'request',
|
transactionType: 'request',
|
||||||
|
@ -61,7 +62,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
||||||
params: {
|
params: {
|
||||||
path: { serviceName: 'opbeans-java' },
|
path: { serviceName: 'opbeans-java' },
|
||||||
query: {
|
query: {
|
||||||
latencyAggregationType: 'avg',
|
latencyAggregationType: LatencyAggregationType.avg,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
transactionType: 'request',
|
transactionType: 'request',
|
||||||
|
@ -130,7 +131,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
||||||
params: {
|
params: {
|
||||||
path: { serviceName: 'opbeans-ruby' },
|
path: { serviceName: 'opbeans-ruby' },
|
||||||
query: {
|
query: {
|
||||||
latencyAggregationType: 'avg',
|
latencyAggregationType: LatencyAggregationType.avg,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
transactionType: 'request',
|
transactionType: 'request',
|
||||||
|
|
|
@ -2680,6 +2680,10 @@
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
uid ""
|
uid ""
|
||||||
|
|
||||||
|
"@kbn/io-ts-utils@link:packages/kbn-io-ts-utils":
|
||||||
|
version "0.0.0"
|
||||||
|
uid ""
|
||||||
|
|
||||||
"@kbn/legacy-logging@link:packages/kbn-legacy-logging":
|
"@kbn/legacy-logging@link:packages/kbn-legacy-logging":
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
uid ""
|
uid ""
|
||||||
|
@ -2712,6 +2716,10 @@
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
uid ""
|
uid ""
|
||||||
|
|
||||||
|
"@kbn/server-route-repository@link:packages/kbn-server-route-repository":
|
||||||
|
version "0.0.0"
|
||||||
|
uid ""
|
||||||
|
|
||||||
"@kbn/std@link:packages/kbn-std":
|
"@kbn/std@link:packages/kbn-std":
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
uid ""
|
uid ""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue