Skip to content

Commit

Permalink
[APM] migrate to io-ts
Browse files Browse the repository at this point in the history
  • Loading branch information
dgieselaar committed Aug 8, 2019
1 parent 85f9ef6 commit ee7ef71
Show file tree
Hide file tree
Showing 12 changed files with 437 additions and 133 deletions.
66 changes: 41 additions & 25 deletions x-pack/legacy/plugins/apm/public/services/rest/apm/error_groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { ErrorDistributionAPIResponse } from '../../../../server/lib/errors/distribution/get_distribution';
import { ErrorGroupAPIResponse } from '../../../../server/lib/errors/get_error_group';
import { ErrorGroupListAPIResponse } from '../../../../server/lib/errors/get_error_groups';
import { callApi } from '../callApi';
import { callApmApi } from '../callApi';
import { UIFilters } from '../../../../typings/ui-filters';

export async function loadErrorGroupList({
Expand All @@ -25,14 +22,20 @@ export async function loadErrorGroupList({
sortField?: string;
sortDirection?: string;
}) {
return callApi<ErrorGroupListAPIResponse>({
pathname: `/api/apm/services/${serviceName}/errors`,
query: {
start,
end,
sortField,
sortDirection,
uiFilters: JSON.stringify(uiFilters)
return callApmApi({
pathname: `/api/apm/services/{serviceName}/errors`,
method: 'GET',
params: {
path: {
serviceName
},
query: {
start,
end,
sortField,
sortDirection,
uiFilters: JSON.stringify(uiFilters)
}
}
});
}
Expand All @@ -50,12 +53,19 @@ export async function loadErrorGroupDetails({
errorGroupId: string;
uiFilters: UIFilters;
}) {
return callApi<ErrorGroupAPIResponse>({
pathname: `/api/apm/services/${serviceName}/errors/${errorGroupId}`,
query: {
start,
end,
uiFilters: JSON.stringify(uiFilters)
return callApmApi({
pathname: '/api/apm/services/{serviceName}/errors/{groupId}',
method: 'GET',
params: {
path: {
serviceName,
groupId: errorGroupId
},
query: {
start,
end,
uiFilters: JSON.stringify(uiFilters)
}
}
});
}
Expand All @@ -73,13 +83,19 @@ export async function loadErrorDistribution({
uiFilters: UIFilters;
errorGroupId?: string;
}) {
return callApi<ErrorDistributionAPIResponse>({
pathname: `/api/apm/services/${serviceName}/errors/distribution`,
query: {
start,
end,
groupId: errorGroupId,
uiFilters: JSON.stringify(uiFilters)
return callApmApi({
pathname: `/api/apm/services/{serviceName}/errors/distribution`,
method: 'GET',
params: {
path: {
serviceName
},
query: {
start,
end,
groupId: errorGroupId,
uiFilters: JSON.stringify(uiFilters)
}
}
});
}
31 changes: 31 additions & 0 deletions x-pack/legacy/plugins/apm/public/services/rest/callApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import LRU from 'lru-cache';
import hash from 'object-hash';
import { kfetch, KFetchOptions } from 'ui/kfetch';
import { KFetchKibanaOptions } from 'ui/kfetch/kfetch';
import { idx } from '@kbn/elastic-idx';
import { APICall, RouteParams } from '../../../server/routes/typings';
import { api } from '../../../server/routes';

function fetchOptionsWithDebug(fetchOptions: KFetchOptions) {
const debugEnabled =
Expand Down Expand Up @@ -55,6 +58,34 @@ export async function callApi<T = void>(
return res;
}

export const callApmApi: APICall<typeof api> = ({
method,
pathname,
params
}: {
method: any;
pathname: string;
params?: {
[key in keyof RouteParams]: any;
};
}) => {
const path = idx(params, _ => _.path) || {};
const query = idx(params, _ => _.query) || {};
const body = idx(params, _ => _.body) || {};

const formattedPathname = pathname
.split('/')
.map(part => (part.startsWith(':') ? path[part.substr(1)] : part))
.join('/');

return callApi({
method,
pathname: formattedPathname,
query,
body: body ? JSON.stringify(body) : undefined
}) as any;
};

// only cache items that has a time range with `start` and `end` params,
// and where `end` is not a timestamp in the future
function isCachable(fetchOptions: KFetchOptions) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export async function getErrorGroups({
setup
}: {
serviceName: string;
sortField: string;
sortDirection: string;
sortField?: string;
sortDirection?: string;
setup: Setup;
}) {
const { start, end, uiFiltersES, client, config } = setup;
Expand Down
7 changes: 3 additions & 4 deletions x-pack/legacy/plugins/apm/server/new-platform/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@
import { InternalCoreSetup } from 'src/core/server';
import { makeApmUsageCollector } from '../lib/apm_telemetry';
import { CoreSetupWithUsageCollector } from '../lib/apm_telemetry/make_apm_usage_collector';
import { initErrorsApi } from '../routes/errors';
import { initMetricsApi } from '../routes/metrics';
import { initServicesApi } from '../routes/services';
import { initTracesApi } from '../routes/traces';
import { initTransactionGroupsApi } from '../routes/transaction_groups';
import { initUIFiltersApi } from '../routes/ui_filters';
import { initIndexPatternApi } from '../routes/index_pattern';
import { initSettingsApi } from '../routes/settings';
import { api } from '../routes';

export class Plugin {
public setup(core: InternalCoreSetup) {
Expand All @@ -23,9 +22,9 @@ export class Plugin {
initTracesApi(core);
initServicesApi(core);
initSettingsApi(core);
initErrorsApi(core);
initMetricsApi(core);
initIndexPatternApi(core);
api.init(core);

makeApmUsageCollector(core as CoreSetupWithUsageCollector);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { flatten } from 'lodash';
import { InternalCoreSetup } from 'src/core/server';
import { initErrorsApi } from '../errors';
// import { initErrorsApi } from '../errors';
import { initServicesApi } from '../services';
import { initTracesApi } from '../traces';

Expand Down Expand Up @@ -65,9 +65,9 @@ describe('route handlers should fail with a Boom error', () => {
consoleErrorSpy.mockRestore();
});

describe('error routes', () => {
testRouteFailures(initErrorsApi);
});
// describe('error routes', () => {
// testRouteFailures(initErrorsApi);
// });

describe('service routes', () => {
testRouteFailures(initServicesApi);
Expand Down
85 changes: 85 additions & 0 deletions x-pack/legacy/plugins/apm/server/routes/create_api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { merge } from 'lodash';
import Boom from 'boom';
import { InternalCoreSetup } from 'src/core/server';
import { Request } from 'hapi';
import * as t from 'io-ts';
import { PathReporter } from 'io-ts/lib/PathReporter';
import { ServerAPI, RouteFactoryFunction, RouteParams } from './typings';
import { removeUndefinedProps } from '../../public/context/UrlParamsContext/helpers';

type GenericRouteFactoryFunction = RouteFactoryFunction<
any,
any,
{ [key in keyof RouteParams]?: RouteParams[key] },
any
>;

export function createApi() {
const factoryFns: GenericRouteFactoryFunction[] = [];
const router = {
add(fn: GenericRouteFactoryFunction) {
factoryFns.push(fn);
return router;
},
init: (core: InternalCoreSetup) => {
const { server } = core.http;
factoryFns.forEach(fn => {
const { params = {}, ...route } = fn(core);
server.route(
merge(
{
options: {
tags: ['access:apm']
}
},
route,
{
handler: async (request: Request) => {
const paramMap = {
path: request.params,
body: request.payload,
query: request.query
};

const parsedParams = (Object.keys(params) as Array<
keyof typeof params
>).reduce(
(acc, key) => {
let codec = params[key];
if (!codec) return acc;

if ('_tag' in codec) {
codec = t.exact(codec);
}

const result = codec.decode(paramMap[key]);
if (result.isLeft()) {
throw Boom.badRequest(PathReporter.report(result)[0]);
}
return {
...acc,
[key]: result.value
};
},
{} as Record<keyof typeof params, any>
);

return route.handler(
request,
removeUndefinedProps(parsedParams)
);
}
}
)
);
});
}
};

return router as ServerAPI<{}>;
}
35 changes: 35 additions & 0 deletions x-pack/legacy/plugins/apm/server/routes/create_route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import * as t from 'io-ts';
import {
RouteFactoryFunction,
HttpMethod,
RouteParams,
RouteRequestHandler
} from './typings';

export function createRoute<
T extends string,
U extends HttpMethod,
V extends RouteParams,
W,
X extends RouteRequestHandler<V, W>
>(cb: RouteFactoryFunction<T, U, V, W>) {
return cb;
}

createRoute(core => ({
path: '/foo',
method: 'GET' as const,
params: {
query: t.type({
serviceName: t.string
})
},
handler: async (req, params) => {
return null;
}
}));
30 changes: 30 additions & 0 deletions x-pack/legacy/plugins/apm/server/routes/default_api_types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import * as t from 'io-ts';
import { either } from 'fp-ts/lib/Either';

const tDate = new t.Type<string, string, unknown>(
'DateAsString',
(u): u is string => typeof u === 'string',
(u, c) =>
either.chain(t.string.validate(u, c), s => {
const d = new Date(s);
return isNaN(d.getTime()) ? t.failure(u, c) : t.success(s);
}),
a => a
);

export const defaultApiTypes = t.intersection([
t.type({
start: tDate,
end: tDate,
uiFilters: t.string
}),
t.partial({
_debug: t.boolean
})
]);
Loading

0 comments on commit ee7ef71

Please sign in to comment.