Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typescript migration for the transaction endpoints #24397

Merged
merged 1 commit into from
Oct 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions x-pack/plugins/apm/public/services/rest/apm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
// @ts-ignore
import { camelizeKeys } from 'humps';
import { isEmpty } from 'lodash';
import { ServiceResponse } from 'x-pack/plugins/apm/server/lib/services/get_service';
import { ServiceListItemResponse } from 'x-pack/plugins/apm/server/lib/services/get_services';
import { IDistributionResponse } from 'x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution';
import { Span } from 'x-pack/plugins/apm/typings/Span';
import { Transaction } from 'x-pack/plugins/apm/typings/Transaction';
Expand Down Expand Up @@ -53,7 +55,11 @@ export async function getEncodedEsQuery(kuery?: string) {
return encodeURIComponent(JSON.stringify(esFilterQuery));
}

export async function loadServiceList({ start, end, kuery }: IUrlParams) {
export async function loadServiceList({
start,
end,
kuery
}: IUrlParams): Promise<ServiceListItemResponse> {
return callApi({
pathname: `/api/apm/services`,
query: {
Expand All @@ -69,7 +75,7 @@ export async function loadServiceDetails({
start,
end,
kuery
}: IUrlParams) {
}: IUrlParams): Promise<ServiceResponse> {
return callApi({
pathname: `/api/apm/services/${serviceName}`,
query: {
Expand Down Expand Up @@ -151,7 +157,7 @@ export async function loadSpans({
start,
end,
transactionId
}: IUrlParams) {
}: IUrlParams): Promise<Span[]> {
const hits: Span[] = await callApi({
pathname: `/api/apm/services/${serviceName}/transactions/${transactionId}/spans`,
query: {
Expand Down Expand Up @@ -189,7 +195,7 @@ export async function loadTransaction({
traceId,
kuery
}: IUrlParams) {
const result = await callApi(
const result: Transaction = await callApi(
{
pathname: `/api/apm/services/${serviceName}/transactions/${transactionId}`,
query: {
Expand Down
26 changes: 14 additions & 12 deletions x-pack/plugins/apm/server/lib/helpers/setup_request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,22 @@

/* tslint:disable no-console */
import { SearchParams, SearchResponse } from 'elasticsearch';
import { Request, Server } from 'hapi';
import moment from 'moment';
import { Url } from 'url';
import { StringMap } from '../../../typings/common';

function decodeEsQuery(esQuery?: string): object {
return esQuery ? JSON.parse(decodeURIComponent(esQuery)) : null;
}

// TODO: get these from hapi
interface Request {
query: StringMap;
server: any;
method: string;
url: Url;
interface KibanaServer extends Server {
config: () => KibanaConfig;
}

interface ServerConfig {
interface KibanaRequest extends Request {
server: KibanaServer;
}

interface KibanaConfig {
get: (key: string) => any;
}

Expand All @@ -31,12 +30,15 @@ type Client<T> = (type: string, params: SearchParams) => SearchResponse<T>;
export interface Setup<T = any> {
start: number;
end: number;
esFilterQuery: object;
esFilterQuery: any;
client: Client<T>;
config: ServerConfig;
config: KibanaConfig;
}

export function setupRequest(req: Request, reply: (setup: Setup) => void) {
export function setupRequest(
req: KibanaRequest,
reply: (setup: Setup) => void
) {
const cluster = req.server.plugins.elasticsearch.getCluster('data');

function client<T>(type: string, params: SearchParams): SearchResponse<T> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface ITransactionGroupSample {
context: Transaction['context'];
}

interface ITransactionGroupBucket {
export interface ITransactionGroupBucket {
key: string;
doc_count: number;
avg: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,25 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { get } from 'lodash';
import { oc } from 'ts-optchain';
import { TermsAggsBucket } from 'x-pack/plugins/apm/typings/elasticsearch';
import {
SERVICE_AGENT_NAME,
SERVICE_NAME,
TRANSACTION_TYPE,
SERVICE_AGENT_NAME
TRANSACTION_TYPE
} from '../../../common/constants';
import { Setup } from '../helpers/setup_request';

export async function getService({ serviceName, setup }) {
export interface ServiceResponse {
service_name: string;
types: string[];
agent_name?: string;
}

export async function getService(
serviceName: string,
setup: Setup
): Promise<ServiceResponse> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interface ServiceResponse is also used in the frontend, so whenever the backend changes, we can make sure it doesn't break anything.

(This currently only works in theory because we use react-redux-request which is written in vanilla js and I didn't add good typings. I'm very keen on moving to suspense whenever that is ready).

const { start, end, esFilterQuery, client, config } = setup;

const params = {
Expand Down Expand Up @@ -52,11 +63,21 @@ export async function getService({ serviceName, setup }) {
params.body.query.bool.filter.push(esFilterQuery);
}

interface Aggs {
types: {
buckets: TermsAggsBucket[];
};
agents: {
buckets: TermsAggsBucket[];
};
}

const resp = await client('search', params);
const aggs: Aggs = resp.aggregations;

return {
service_name: serviceName,
types: resp.aggregations.types.buckets.map(bucket => bucket.key),
agent_name: get(resp, 'aggregations.agents.buckets[0].key')
types: aggs.types.buckets.map(bucket => bucket.key),
agent_name: oc(aggs).agents.buckets[0].key()
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,27 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { oc } from 'ts-optchain';
import { TermsAggsBucket } from 'x-pack/plugins/apm/typings/elasticsearch';
import {
SERVICE_NAME,
TRANSACTION_DURATION,
PROCESSOR_EVENT,
SERVICE_AGENT_NAME,
PROCESSOR_EVENT
SERVICE_NAME,
TRANSACTION_DURATION
} from '../../../common/constants';
import { get } from 'lodash';
import { Setup } from '../helpers/setup_request';

export interface ServiceListItemResponse {
service_name: string;
agent_name: string | undefined;
transactions_per_minute: number;
errors_per_minute: number;
avg_response_time: number;
}

export async function getServices({ setup }) {
export async function getServices(
setup: Setup
): Promise<ServiceListItemResponse[]> {
const { start, end, esFilterQuery, client, config } = setup;

const params = {
Expand Down Expand Up @@ -71,26 +83,43 @@ export async function getServices({ setup }) {
params.body.query.bool.filter.push(esFilterQuery);
}

interface ServiceBucket extends TermsAggsBucket {
avg: {
value: number;
};
agents: {
buckets: TermsAggsBucket[];
};
events: {
buckets: TermsAggsBucket[];
};
}

interface Aggs extends TermsAggsBucket {
services: {
buckets: ServiceBucket[];
};
}

const resp = await client('search', params);
const aggs: Aggs = resp.aggregations;
const serviceBuckets = oc(aggs).services.buckets([]);

const buckets = get(resp.aggregations, 'services.buckets', []);
return buckets.map(bucket => {
return serviceBuckets.map(bucket => {
const eventTypes = bucket.events.buckets;

const transactions = eventTypes.find(e => e.key === 'transaction');
const totalTransactions = get(transactions, 'doc_count', 0);
const totalTransactions = oc(transactions).doc_count(0);

const errors = eventTypes.find(e => e.key === 'error');
const totalErrors = get(errors, 'doc_count', 0);
const totalErrors = oc(errors).doc_count(0);

const deltaAsMinutes = (end - start) / 1000 / 60;

const transactionsPerMinute = totalTransactions / deltaAsMinutes;
const errorsPerMinute = totalErrors / deltaAsMinutes;

return {
service_name: bucket.key,
agent_name: get(bucket, 'agents.buckets[0].key', null),
agent_name: oc(bucket).agents.buckets[0].key(),
transactions_per_minute: transactionsPerMinute,
errors_per_minute: errorsPerMinute,
avg_response_time: bucket.avg.value
Expand Down
18 changes: 9 additions & 9 deletions x-pack/plugins/apm/server/lib/traces/get_top_traces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,22 @@
*/

import { SearchResponse } from 'elasticsearch';
import { oc } from 'ts-optchain';
import { get } from 'lodash';
import {
PARENT_ID,
PROCESSOR_EVENT,
TRACE_ID
} from '../../../common/constants';
import { StringMap } from '../../../typings/common';
import { Transaction } from '../../../typings/Transaction';
import { ITransactionGroup } from '../../../typings/TransactionGroup';
import { Setup } from '../helpers/setup_request';
import {
ITransactionGroupBucket,
prepareTransactionGroups,
TRANSACTION_GROUP_AGGREGATES
} from '../helpers/transaction_group_query';

export async function getTopTraces({
setup
}: {
setup: StringMap<any>;
}): Promise<ITransactionGroup[]> {
export async function getTopTraces(setup: Setup): Promise<ITransactionGroup[]> {
const { start, end, esFilterQuery, client, config } = setup;

const params = {
Expand Down Expand Up @@ -70,8 +67,11 @@ export async function getTopTraces({
}

const response: SearchResponse<Transaction> = await client('search', params);
// @ts-ignore
const buckets = oc(response).aggregations.transactions.buckets([]);
const buckets: ITransactionGroupBucket[] = get(
response.aggregations,
'transactions.buckets',
[]
);

return prepareTransactionGroups({ buckets, start, end });
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { SearchParams, SearchResponse } from 'elasticsearch';
import { oc } from 'ts-optchain';
import { Transaction } from 'x-pack/plugins/apm/typings/Transaction';
import {
TRANSACTION_ID,
PROCESSOR_EVENT,
TRACE_ID
TRACE_ID,
TRANSACTION_ID
} from '../../../common/constants';
import { get } from 'lodash';
import { Setup } from '../helpers/setup_request';

async function getTransaction({ transactionId, traceId, setup }) {
export async function getTransaction(
transactionId: string,
traceId: string | undefined,
setup: Setup
) {
const { start, end, esFilterQuery, client, config } = setup;

const params = {
const params: SearchParams = {
index: config.get('apm_oss.transactionIndices'),
body: {
size: 1,
Expand Down Expand Up @@ -46,8 +53,6 @@ async function getTransaction({ transactionId, traceId, setup }) {
params.body.query.bool.filter.push({ term: { [TRACE_ID]: traceId } });
}

const resp = await client('search', params);
return get(resp, 'hits.hits[0]._source', {});
const resp: SearchResponse<Transaction> = await client('search', params);
return oc(resp).hits.hits[0]._source();
}

export default getTransaction;
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@
*/

import Boom from 'boom';
import { getServices } from '../lib/services/get_services';
import { getService } from '../lib/services/get_service';
import { setupRequest } from '../lib/helpers/setup_request';
import { IReply, Request, Server } from 'hapi';
import { withDefaultValidators } from '../lib/helpers/input_validation';
import { setupRequest } from '../lib/helpers/setup_request';
import { getService } from '../lib/services/get_service';
import { getServices } from '../lib/services/get_services';

const ROOT = '/api/apm/services';
const pre = [{ method: setupRequest, assign: 'setup' }];
const defaultErrorHandler = reply => err => {
const defaultErrorHandler = (reply: IReply) => (err: Error) => {
// tslint:disable-next-line
console.error(err.stack);
// @ts-ignore
reply(Boom.wrap(err, 400));
};

export function initServicesApi(server) {
export function initServicesApi(server: Server) {
server.route({
method: 'GET',
path: ROOT,
Expand All @@ -27,9 +30,9 @@ export function initServicesApi(server) {
query: withDefaultValidators()
}
},
handler: (req, reply) => {
handler: (req: Request, reply: IReply) => {
const { setup } = req.pre;
return getServices({ setup })
return getServices(setup)
.then(reply)
.catch(defaultErrorHandler(reply));
}
Expand All @@ -44,10 +47,10 @@ export function initServicesApi(server) {
query: withDefaultValidators()
}
},
handler: (req, reply) => {
handler: (req: Request, reply: IReply) => {
const { setup } = req.pre;
const { serviceName } = req.params;
return getService({ serviceName, setup })
return getService(serviceName, setup)
.then(reply)
.catch(defaultErrorHandler(reply));
}
Expand Down
Loading