diff --git a/x-pack/plugins/apm/index.js b/x-pack/plugins/apm/index.js index c6844f30f62c..5337aa581727 100644 --- a/x-pack/plugins/apm/index.js +++ b/x-pack/plugins/apm/index.js @@ -6,6 +6,7 @@ import { resolve } from 'path'; import { initTransactionsApi } from './server/routes/transactions'; +import { initTransactionGroupsApi } from './server/routes/transaction_groups'; import { initServicesApi } from './server/routes/services'; import { initErrorsApi } from './server/routes/errors'; import { initStatusApi } from './server/routes/status_check'; @@ -64,6 +65,7 @@ export function apm(kibana) { init(server) { initTransactionsApi(server); + initTransactionGroupsApi(server); initTracesApi(server); initServicesApi(server); initErrorsApi(server); diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.js b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.js index 2f93eb0b2f1f..2a6d3f8e5fd2 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.js +++ b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.js @@ -8,9 +8,8 @@ import React from 'react'; import { shallow } from 'enzyme'; import { ServiceOverview } from '../view'; import { STATUS } from '../../../../constants'; -import * as apmRestServices from '../../../../services/rest/apm'; - -jest.mock('../../../../services/rest/apm'); +import * as apmRestServices from '../../../../services/rest/apm/status_check'; +jest.mock('../../../../services/rest/apm/status_check'); describe('Service Overview -> View', () => { let mockAgentStatus; @@ -22,7 +21,6 @@ describe('Service Overview -> View', () => { dataFound: true }; - // eslint-disable-next-line import/namespace apmRestServices.loadAgentStatus = jest.fn(() => Promise.resolve(mockAgentStatus) ); diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/view.tsx b/x-pack/plugins/apm/public/components/app/ServiceOverview/view.tsx index 2bd9373b94c3..46be1e85b880 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/view.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceOverview/view.tsx @@ -9,7 +9,7 @@ import React, { Component } from 'react'; import { RRRRenderResponse } from 'react-redux-request'; import { IUrlParams } from 'x-pack/plugins/apm/public/store/urlParams'; import { IServiceListItem } from 'x-pack/plugins/apm/server/lib/services/get_services'; -import { loadAgentStatus } from '../../../services/rest/apm'; +import { loadAgentStatus } from '../../../services/rest/apm/status_check'; import { ServiceListRequest } from '../../../store/reactReduxRequest/serviceList'; import { EmptyMessage } from '../../shared/EmptyMessage'; import { SetupInstructionsLink } from '../../shared/SetupInstructionsLink'; diff --git a/x-pack/plugins/apm/public/services/rest/apm.ts b/x-pack/plugins/apm/public/services/rest/apm.ts deleted file mode 100644 index cddd07bf4603..000000000000 --- a/x-pack/plugins/apm/public/services/rest/apm.ts +++ /dev/null @@ -1,314 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -// @ts-ignore -import { camelizeKeys } from 'humps'; -import { ServiceAPIResponse } from 'x-pack/plugins/apm/server/lib/services/get_service'; -import { ServiceListAPIResponse } from 'x-pack/plugins/apm/server/lib/services/get_services'; -import { TraceListAPIResponse } from 'x-pack/plugins/apm/server/lib/traces/get_top_traces'; -import { TraceAPIResponse } from 'x-pack/plugins/apm/server/lib/traces/get_trace'; -import { TimeSeriesAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/charts'; -import { ITransactionDistributionAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/distribution'; -import { TransactionListAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/get_top_transactions'; -import { TransactionAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/get_transaction'; -import { SpanListAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/spans/get_spans'; -import { Span } from 'x-pack/plugins/apm/typings/Span'; -import { Transaction } from 'x-pack/plugins/apm/typings/Transaction'; -import { IUrlParams } from '../../store/urlParams'; -// @ts-ignore -import { convertKueryToEsQuery } from '../kuery'; -import { callApi } from './callApi'; -// @ts-ignore -import { getAPMIndexPattern } from './savedObjects'; - -export async function loadLicense() { - return callApi({ - pathname: `/api/xpack/v1/info` - }); -} - -export async function loadServerStatus() { - return callApi({ - pathname: `/api/apm/status/server` - }); -} - -export async function loadAgentStatus() { - return callApi<{ dataFound: boolean }>({ - pathname: `/api/apm/status/agent` - }); -} - -export async function getEncodedEsQuery(kuery?: string) { - if (!kuery) { - return; - } - - const indexPattern = await getAPMIndexPattern(); - - if (!indexPattern) { - return; - } - - const esFilterQuery = convertKueryToEsQuery(kuery, indexPattern); - return encodeURIComponent(JSON.stringify(esFilterQuery)); -} - -export async function loadServiceList({ start, end, kuery }: IUrlParams) { - return callApi({ - pathname: `/api/apm/services`, - query: { - start, - end, - esFilterQuery: await getEncodedEsQuery(kuery) - } - }); -} - -export async function loadServiceDetails({ - serviceName, - start, - end, - kuery -}: IUrlParams) { - return callApi({ - pathname: `/api/apm/services/${serviceName}`, - query: { - start, - end, - esFilterQuery: await getEncodedEsQuery(kuery) - } - }); -} - -export async function loadTraceList({ start, end, kuery }: IUrlParams) { - const groups = await callApi({ - pathname: '/api/apm/traces', - query: { - start, - end, - esFilterQuery: await getEncodedEsQuery(kuery) - } - }); - - return groups.map(group => { - group.sample = addVersion(group.sample); - return group; - }); -} - -export async function loadTransactionList({ - serviceName, - start, - end, - kuery, - transactionType -}: IUrlParams) { - const groups = await callApi({ - pathname: `/api/apm/services/${serviceName}/transactions`, - query: { - start, - end, - esFilterQuery: await getEncodedEsQuery(kuery), - transaction_type: transactionType - } - }); - - return groups.map(group => { - group.sample = addVersion(group.sample); - return group; - }); -} - -export async function loadTransactionDistribution({ - serviceName, - start, - end, - transactionName, - transactionId, - kuery -}: IUrlParams) { - return callApi({ - pathname: `/api/apm/services/${serviceName}/transactions/distribution`, - query: { - start, - end, - transaction_name: transactionName, - transaction_id: transactionId, - esFilterQuery: await getEncodedEsQuery(kuery) - } - }); -} - -function addVersion( - item: T -): T { - if (item != null) { - item.version = item.hasOwnProperty('trace') ? 'v2' : 'v1'; - } - - return item; -} - -function addSpanId(hit: Span, i: number) { - if (!hit.span.id) { - hit.span.id = i; - } - return hit; -} - -export async function loadSpans({ - serviceName, - start, - end, - transactionId -}: IUrlParams) { - const hits = await callApi({ - pathname: `/api/apm/services/${serviceName}/transactions/${transactionId}/spans`, - query: { - start, - end - } - }); - - return hits.map(addVersion).map(addSpanId); -} - -export async function loadTrace({ traceId, start, end }: IUrlParams) { - const hits = await callApi( - { - pathname: `/api/apm/traces/${traceId}`, - query: { - start, - end - } - }, - { - camelcase: false - } - ); - - return hits.map(addVersion); -} - -export async function loadTransaction({ - serviceName, - start, - end, - transactionId, - traceId, - kuery -}: IUrlParams) { - const result = await callApi( - { - pathname: `/api/apm/services/${serviceName}/transactions/${transactionId}`, - query: { - traceId, - start, - end, - esFilterQuery: await getEncodedEsQuery(kuery) - } - }, - { - camelcase: false - } - ); - - return addVersion(result); -} - -export async function loadCharts({ - serviceName, - start, - end, - kuery, - transactionType, - transactionName -}: IUrlParams) { - return callApi({ - pathname: `/api/apm/services/${serviceName}/transactions/charts`, - query: { - start, - end, - esFilterQuery: await getEncodedEsQuery(kuery), - transaction_type: transactionType, - transaction_name: transactionName - } - }); -} - -interface ErrorGroupListParams extends IUrlParams { - size: number; - sortField: string; - sortDirection: string; -} - -export async function loadErrorGroupList({ - serviceName, - start, - end, - kuery, - size, - sortField, - sortDirection -}: ErrorGroupListParams) { - return callApi({ - pathname: `/api/apm/services/${serviceName}/errors`, - query: { - start, - end, - size, - sortField, - sortDirection, - esFilterQuery: await getEncodedEsQuery(kuery) - } - }); -} - -export async function loadErrorGroupDetails({ - serviceName, - start, - end, - kuery, - errorGroupId -}: IUrlParams) { - // TODO: add types when error section is converted to ts - const res = await callApi( - { - pathname: `/api/apm/services/${serviceName}/errors/${errorGroupId}`, - query: { - start, - end, - esFilterQuery: await getEncodedEsQuery(kuery) - } - }, - { - camelcase: false - } - ); - const camelizedRes: any = camelizeKeys(res); - if (res.error.context) { - camelizedRes.error.context = res.error.context; - } - return camelizedRes; -} - -export async function loadErrorDistribution({ - serviceName, - start, - end, - kuery, - errorGroupId -}: IUrlParams) { - return callApi({ - pathname: `/api/apm/services/${serviceName}/errors/${errorGroupId}/distribution`, - query: { - start, - end, - esFilterQuery: await getEncodedEsQuery(kuery) - } - }); -} diff --git a/x-pack/plugins/apm/public/services/rest/apm/apm.ts b/x-pack/plugins/apm/public/services/rest/apm/apm.ts new file mode 100644 index 000000000000..b43c8e73df19 --- /dev/null +++ b/x-pack/plugins/apm/public/services/rest/apm/apm.ts @@ -0,0 +1,36 @@ +/* + * 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 { Span } from 'x-pack/plugins/apm/typings/Span'; +import { Transaction } from 'x-pack/plugins/apm/typings/Transaction'; +// @ts-ignore +import { convertKueryToEsQuery } from '../../kuery'; +// @ts-ignore +import { getAPMIndexPattern } from '../savedObjects'; + +export async function getEncodedEsQuery(kuery?: string) { + if (!kuery) { + return; + } + + const indexPattern = await getAPMIndexPattern(); + if (!indexPattern) { + return; + } + + const esFilterQuery = convertKueryToEsQuery(kuery, indexPattern); + return encodeURIComponent(JSON.stringify(esFilterQuery)); +} + +export function addVersion( + item: T +): T { + if (item != null) { + item.version = item.hasOwnProperty('trace') ? 'v2' : 'v1'; + } + + return item; +} diff --git a/x-pack/plugins/apm/public/services/rest/apm/error_groups.ts b/x-pack/plugins/apm/public/services/rest/apm/error_groups.ts new file mode 100644 index 000000000000..7043f83129c0 --- /dev/null +++ b/x-pack/plugins/apm/public/services/rest/apm/error_groups.ts @@ -0,0 +1,83 @@ +/* + * 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 { camelizeKeys } from 'humps'; +import { IUrlParams } from '../../../store/urlParams'; +import { callApi } from '../callApi'; +import { getEncodedEsQuery } from './apm'; + +interface ErrorGroupListParams extends IUrlParams { + size: number; + sortField: string; + sortDirection: string; +} + +export async function loadErrorGroupList({ + serviceName, + start, + end, + kuery, + size, + sortField, + sortDirection +}: ErrorGroupListParams) { + return callApi({ + pathname: `/api/apm/services/${serviceName}/errors`, + query: { + start, + end, + size, + sortField, + sortDirection, + esFilterQuery: await getEncodedEsQuery(kuery) + } + }); +} + +export async function loadErrorGroupDetails({ + serviceName, + start, + end, + kuery, + errorGroupId +}: IUrlParams) { + // TODO: add types when error section is converted to ts + const res = await callApi( + { + pathname: `/api/apm/services/${serviceName}/errors/${errorGroupId}`, + query: { + start, + end, + esFilterQuery: await getEncodedEsQuery(kuery) + } + }, + { + camelcase: false + } + ); + const camelizedRes: any = camelizeKeys(res); + if (res.error.context) { + camelizedRes.error.context = res.error.context; + } + return camelizedRes; +} + +export async function loadErrorDistribution({ + serviceName, + start, + end, + kuery, + errorGroupId +}: IUrlParams) { + return callApi({ + pathname: `/api/apm/services/${serviceName}/errors/${errorGroupId}/distribution`, + query: { + start, + end, + esFilterQuery: await getEncodedEsQuery(kuery) + } + }); +} diff --git a/x-pack/plugins/apm/public/services/rest/apm/services.ts b/x-pack/plugins/apm/public/services/rest/apm/services.ts new file mode 100644 index 000000000000..430d2fa21770 --- /dev/null +++ b/x-pack/plugins/apm/public/services/rest/apm/services.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ServiceAPIResponse } from 'x-pack/plugins/apm/server/lib/services/get_service'; +import { ServiceListAPIResponse } from 'x-pack/plugins/apm/server/lib/services/get_services'; +import { IUrlParams } from '../../../store/urlParams'; +import { callApi } from '../callApi'; +import { getEncodedEsQuery } from './apm'; + +export async function loadServiceList({ start, end, kuery }: IUrlParams) { + return callApi({ + pathname: `/api/apm/services`, + query: { + start, + end, + esFilterQuery: await getEncodedEsQuery(kuery) + } + }); +} + +export async function loadServiceDetails({ + serviceName, + start, + end, + kuery +}: IUrlParams) { + return callApi({ + pathname: `/api/apm/services/${serviceName}`, + query: { + start, + end, + esFilterQuery: await getEncodedEsQuery(kuery) + } + }); +} diff --git a/x-pack/plugins/apm/public/services/rest/apm/status_check.ts b/x-pack/plugins/apm/public/services/rest/apm/status_check.ts new file mode 100644 index 000000000000..30fe628d8e7f --- /dev/null +++ b/x-pack/plugins/apm/public/services/rest/apm/status_check.ts @@ -0,0 +1,19 @@ +/* + * 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 { callApi } from '../callApi'; + +export async function loadServerStatus() { + return callApi({ + pathname: `/api/apm/status/server` + }); +} + +export async function loadAgentStatus() { + return callApi<{ dataFound: boolean }>({ + pathname: `/api/apm/status/agent` + }); +} diff --git a/x-pack/plugins/apm/public/services/rest/apm/traces.ts b/x-pack/plugins/apm/public/services/rest/apm/traces.ts new file mode 100644 index 000000000000..87b00a2aab1e --- /dev/null +++ b/x-pack/plugins/apm/public/services/rest/apm/traces.ts @@ -0,0 +1,44 @@ +/* + * 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 { TraceListAPIResponse } from 'x-pack/plugins/apm/server/lib/traces/get_top_traces'; +import { TraceAPIResponse } from 'x-pack/plugins/apm/server/lib/traces/get_trace'; +import { IUrlParams } from '../../../store/urlParams'; +import { callApi } from '../callApi'; +import { addVersion, getEncodedEsQuery } from './apm'; + +export async function loadTrace({ traceId, start, end }: IUrlParams) { + const hits = await callApi( + { + pathname: `/api/apm/traces/${traceId}`, + query: { + start, + end + } + }, + { + camelcase: false + } + ); + + return hits.map(addVersion); +} + +export async function loadTraceList({ start, end, kuery }: IUrlParams) { + const groups = await callApi({ + pathname: '/api/apm/traces', + query: { + start, + end, + esFilterQuery: await getEncodedEsQuery(kuery) + } + }); + + return groups.map(group => { + group.sample = addVersion(group.sample); + return group; + }); +} diff --git a/x-pack/plugins/apm/public/services/rest/apm/transaction_groups.ts b/x-pack/plugins/apm/public/services/rest/apm/transaction_groups.ts new file mode 100644 index 000000000000..47aff73264d0 --- /dev/null +++ b/x-pack/plugins/apm/public/services/rest/apm/transaction_groups.ts @@ -0,0 +1,93 @@ +/* + * 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 { TimeSeriesAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/charts'; +import { ITransactionDistributionAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/distribution'; +import { TransactionListAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/get_top_transactions'; +import { IUrlParams } from '../../../store/urlParams'; +import { callApi } from '../callApi'; +import { addVersion, getEncodedEsQuery } from './apm'; + +export async function loadTransactionList({ + serviceName, + start, + end, + kuery, + transactionType = 'request' +}: IUrlParams) { + const groups = await callApi({ + pathname: `/api/apm/services/${serviceName}/transaction_groups/${transactionType}`, + query: { + start, + end, + esFilterQuery: await getEncodedEsQuery(kuery) + } + }); + + return groups.map(group => { + group.sample = addVersion(group.sample); + return group; + }); +} + +export async function loadTransactionDistribution({ + serviceName, + start, + end, + transactionName, + transactionType = 'request', + transactionId, + kuery +}: Required) { + return callApi({ + pathname: `/api/apm/services/${serviceName}/transaction_groups/${transactionType}/${encodeURIComponent( + transactionName + )}/distribution`, + query: { + start, + end, + transactionId, + esFilterQuery: await getEncodedEsQuery(kuery) + } + }); +} + +export async function loadDetailsCharts({ + serviceName, + start, + end, + kuery, + transactionType = 'request', + transactionName +}: Required) { + return callApi({ + pathname: `/api/apm/services/${serviceName}/transaction_groups/${transactionType}/${encodeURIComponent( + transactionName + )}/charts`, + query: { + start, + end, + esFilterQuery: await getEncodedEsQuery(kuery) + } + }); +} + +export async function loadOverviewCharts({ + serviceName, + start, + end, + kuery, + transactionType = 'request' +}: IUrlParams) { + return callApi({ + pathname: `/api/apm/services/${serviceName}/transaction_groups/${transactionType}/charts`, + query: { + start, + end, + esFilterQuery: await getEncodedEsQuery(kuery) + } + }); +} diff --git a/x-pack/plugins/apm/public/services/rest/apm/transactions.ts b/x-pack/plugins/apm/public/services/rest/apm/transactions.ts new file mode 100644 index 000000000000..7f2f23476db4 --- /dev/null +++ b/x-pack/plugins/apm/public/services/rest/apm/transactions.ts @@ -0,0 +1,62 @@ +/* + * 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 { TransactionAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/get_transaction'; +import { SpanListAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/spans/get_spans'; +import { Span } from 'x-pack/plugins/apm/typings/Span'; +import { IUrlParams } from '../../../store/urlParams'; +import { callApi } from '../callApi'; +import { addVersion, getEncodedEsQuery } from './apm'; + +export async function loadSpans({ + serviceName, + start, + end, + transactionId +}: IUrlParams) { + const hits = await callApi({ + pathname: `/api/apm/services/${serviceName}/transactions/${transactionId}/spans`, + query: { + start, + end + } + }); + + return hits.map(addVersion).map(addSpanId); +} + +function addSpanId(hit: Span, i: number) { + if (!hit.span.id) { + hit.span.id = i; + } + return hit; +} + +export async function loadTransaction({ + serviceName, + start, + end, + transactionId, + traceId, + kuery +}: IUrlParams) { + const result = await callApi( + { + pathname: `/api/apm/services/${serviceName}/transactions/${transactionId}`, + query: { + traceId, + start, + end, + esFilterQuery: await getEncodedEsQuery(kuery) + } + }, + { + camelcase: false + } + ); + + return addVersion(result); +} diff --git a/x-pack/plugins/apm/public/services/rest/xpack.ts b/x-pack/plugins/apm/public/services/rest/xpack.ts new file mode 100644 index 000000000000..736b63c6fd2e --- /dev/null +++ b/x-pack/plugins/apm/public/services/rest/xpack.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { callApi } from './callApi'; + +export async function loadLicense() { + return callApi({ + pathname: `/api/xpack/v1/info` + }); +} diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/__jest__/serviceList.test.js b/x-pack/plugins/apm/public/store/reactReduxRequest/__jest__/serviceList.test.js index 824196fbf878..b45d3bbc76b1 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/__jest__/serviceList.test.js +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/__jest__/serviceList.test.js @@ -5,7 +5,7 @@ */ import React from 'react'; -import * as rest from '../../../services/rest/apm'; +import * as rest from '../../../services/rest/apm/services'; import { getServiceList, ServiceListRequest } from '../serviceList'; import { mountWithStore } from '../../../utils/testHelpers'; diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/errorDistribution.js b/x-pack/plugins/apm/public/store/reactReduxRequest/errorDistribution.js index a800b31f9b3b..a46449e7f614 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/errorDistribution.js +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/errorDistribution.js @@ -6,7 +6,7 @@ import React from 'react'; import { Request } from 'react-redux-request'; -import { loadErrorDistribution } from '../../services/rest/apm'; +import { loadErrorDistribution } from '../../services/rest/apm/error_groups'; import { createInitialDataSelector } from './helpers'; const ID = 'errorDistribution'; diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/errorGroup.js b/x-pack/plugins/apm/public/store/reactReduxRequest/errorGroup.js index 7913bbe83873..471761578354 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/errorGroup.js +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/errorGroup.js @@ -7,7 +7,7 @@ import React from 'react'; import { createInitialDataSelector } from './helpers'; import { Request } from 'react-redux-request'; -import { loadErrorGroupDetails } from '../../services/rest/apm'; +import { loadErrorGroupDetails } from '../../services/rest/apm/error_groups'; const ID = 'errorGroupDetails'; const INITIAL_DATA = {}; diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/errorGroupList.js b/x-pack/plugins/apm/public/store/reactReduxRequest/errorGroupList.js index 3d566940f704..9863d5f78352 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/errorGroupList.js +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/errorGroupList.js @@ -7,7 +7,7 @@ import React from 'react'; import { createInitialDataSelector } from './helpers'; import { Request } from 'react-redux-request'; -import { loadErrorGroupList } from '../../services/rest/apm'; +import { loadErrorGroupList } from '../../services/rest/apm/error_groups'; const ID = 'errorGroupList'; const INITIAL_DATA = []; diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/license.js b/x-pack/plugins/apm/public/store/reactReduxRequest/license.js index 148f73544a66..8f24d3bcd877 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/license.js +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/license.js @@ -6,7 +6,7 @@ import React from 'react'; import { createInitialDataSelector } from './helpers'; import { Request } from 'react-redux-request'; -import { loadLicense } from '../../services/rest/apm'; +import { loadLicense } from '../../services/rest/xpack'; const ID = 'license'; const INITIAL_DATA = { diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/serviceDetails.js b/x-pack/plugins/apm/public/store/reactReduxRequest/serviceDetails.js index ea8dc67e7649..bb31f94c4b45 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/serviceDetails.js +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/serviceDetails.js @@ -9,7 +9,7 @@ import _ from 'lodash'; import PropTypes from 'prop-types'; import { createInitialDataSelector } from './helpers'; import { Request } from 'react-redux-request'; -import { loadServiceDetails } from '../../services/rest/apm'; +import { loadServiceDetails } from '../../services/rest/apm/services'; const ID = 'serviceDetails'; const INITIAL_DATA = { types: [] }; diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/serviceList.tsx b/x-pack/plugins/apm/public/store/reactReduxRequest/serviceList.tsx index ef5e59649b76..88020ba3537f 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/serviceList.tsx +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/serviceList.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { Request, RRRRender, RRRRenderResponse } from 'react-redux-request'; import { IServiceListItem } from 'x-pack/plugins/apm/server/lib/services/get_services'; -import { loadServiceList } from '../../services/rest/apm'; +import { loadServiceList } from '../../services/rest/apm/services'; import { IReduxState } from '../rootReducer'; import { IUrlParams } from '../urlParams'; // @ts-ignore diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/traceList.js b/x-pack/plugins/apm/public/store/reactReduxRequest/traceList.js index 804e40418986..83ac94887e44 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/traceList.js +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/traceList.js @@ -7,7 +7,7 @@ import React from 'react'; import { Request } from 'react-redux-request'; import { createSelector } from 'reselect'; -import { loadTraceList } from '../../services/rest/apm'; +import { loadTraceList } from '../../services/rest/apm/traces'; import { createInitialDataSelector } from './helpers'; const ID = 'traceList'; diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetails.tsx b/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetails.tsx index 0e1d5ece186d..eab1d2390061 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetails.tsx +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetails.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { Request, RRRRender } from 'react-redux-request'; import { Transaction } from 'x-pack/plugins/apm/typings/Transaction'; -import { loadTransaction } from '../../services/rest/apm'; +import { loadTransaction } from '../../services/rest/apm/transactions'; import { IReduxState } from '../rootReducer'; import { IUrlParams } from '../urlParams'; // @ts-ignore diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetailsCharts.tsx b/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetailsCharts.tsx index a1872adcd529..437a8c8316df 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetailsCharts.tsx +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetailsCharts.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { Request, RRRRender } from 'react-redux-request'; import { createSelector } from 'reselect'; import { TimeSeriesAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/charts'; -import { loadCharts } from '../../services/rest/apm'; +import { loadDetailsCharts } from '../../services/rest/apm/transaction_groups'; import { IReduxState } from '../rootReducer'; import { getCharts } from '../selectors/chartSelectors'; import { getUrlParams, IUrlParams } from '../urlParams'; @@ -61,7 +61,7 @@ export function TransactionDetailsChartsRequest({ urlParams, render }: Props) { return ( { ).toMatchSnapshot(); }); - fit('should transform response correctly', () => { + it('should transform response correctly', () => { const bucket = { key: 'POST /api/orders', doc_count: 180, avg: { value: 255966.30555555556 }, p95: { values: { '95.0': 320238.5 } }, + sum: { value: 3000000000 }, sample: { hits: { total: 180, @@ -54,4 +55,27 @@ describe('transactionGroupsTransformer', () => { } ]); }); + + it('should calculate impact from sum', () => { + const getBucket = (sum: number) => ({ + key: 'POST /api/orders', + doc_count: 180, + avg: { value: 300000 }, + p95: { values: { '95.0': 320000 } }, + sum: { value: sum }, + sample: { hits: { total: 180, hits: [{ _source: 'sample source' }] } } + }); + + const response = ({ + aggregations: { + transactions: { buckets: [getBucket(10), getBucket(20), getBucket(50)] } + } + } as unknown) as ESResponse; + + expect( + transactionGroupsTransformer({ response, start: 100, end: 20000 }).map( + bucket => bucket.impact + ) + ).toEqual([0, 25, 100]); + }); }); diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/transform.ts b/x-pack/plugins/apm/server/lib/transaction_groups/transform.ts index 229134025b18..53f8ca04d4ee 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/transform.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/transform.ts @@ -44,7 +44,7 @@ export function transactionGroupsTransformer({ const results = buckets.map(bucket => { const averageResponseTime = bucket.avg.value; const transactionsPerMinute = bucket.doc_count / minutes; - const impact = Math.round(averageResponseTime * transactionsPerMinute); + const impact = bucket.sum.value; const sample = bucket.sample.hits.hits[0]._source; return { diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/calculate_bucket_size.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/calculate_bucket_size.ts index cb0209f648b9..78c72f39f6ed 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/calculate_bucket_size.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/calculate_bucket_size.ts @@ -8,13 +8,15 @@ import { SearchParams } from 'elasticsearch'; import { SERVICE_NAME, TRANSACTION_DURATION, - TRANSACTION_NAME + TRANSACTION_NAME, + TRANSACTION_TYPE } from '../../../../common/constants'; import { Setup } from '../../helpers/setup_request'; export async function calculateBucketSize( serviceName: string, transactionName: string, + transactionType: string, setup: Setup ) { const { start, end, esFilterQuery, client, config } = setup; @@ -27,6 +29,7 @@ export async function calculateBucketSize( bool: { filter: [ { term: { [SERVICE_NAME]: serviceName } }, + { term: { [TRANSACTION_TYPE]: transactionType } }, { term: { [`${TRANSACTION_NAME}.keyword`]: transactionName } }, { range: { diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts index 55212f716316..746606947aa6 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts @@ -11,7 +11,8 @@ import { TRANSACTION_DURATION, TRANSACTION_ID, TRANSACTION_NAME, - TRANSACTION_SAMPLED + TRANSACTION_SAMPLED, + TRANSACTION_TYPE } from 'x-pack/plugins/apm/common/constants'; import { Setup } from 'x-pack/plugins/apm/server/lib/helpers/setup_request'; import { Transaction } from 'x-pack/plugins/apm/typings/Transaction'; @@ -38,6 +39,7 @@ export type ESResponse = AggregationSearchResponse; export function bucketFetcher( serviceName: string, transactionName: string, + transactionType: string, transactionId: string, bucketSize: number, setup: Setup @@ -52,6 +54,7 @@ export function bucketFetcher( bool: { filter: [ { term: { [SERVICE_NAME]: serviceName } }, + { term: { [TRANSACTION_TYPE]: transactionType } }, { term: { [`${TRANSACTION_NAME}.keyword`]: transactionName } }, { range: { diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts index debc639104d9..8b5ffce6c1b1 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts @@ -11,6 +11,7 @@ import { bucketTransformer } from './transform'; export async function getBuckets( serviceName: string, transactionName: string, + transactionType: string, transactionId: string, bucketSize: number, setup: Setup @@ -18,6 +19,7 @@ export async function getBuckets( const response = await bucketFetcher( serviceName, transactionName, + transactionType, transactionId, bucketSize, setup diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/index.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/index.ts index 25c19f11d58c..16e46191b5f4 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/index.ts @@ -19,18 +19,21 @@ export interface ITransactionDistributionAPIResponse { export async function getDistribution( serviceName: string, transactionName: string, + transactionType: string, transactionId: string, setup: Setup ): Promise { const bucketSize = await calculateBucketSize( serviceName, transactionName, + transactionType, setup ); const { defaultSample, buckets, totalHits } = await getBuckets( serviceName, transactionName, + transactionType, transactionId, bucketSize, setup diff --git a/x-pack/plugins/apm/server/routes/transaction_groups.ts b/x-pack/plugins/apm/server/routes/transaction_groups.ts new file mode 100644 index 000000000000..23f65eeb1819 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/transaction_groups.ts @@ -0,0 +1,110 @@ +/* + * 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 Boom from 'boom'; +import { Server } from 'hapi'; +import Joi from 'joi'; +import { withDefaultValidators } from '../lib/helpers/input_validation'; +import { setupRequest } from '../lib/helpers/setup_request'; +import { getChartsData } from '../lib/transactions/charts'; +import { getDistribution } from '../lib/transactions/distribution'; +import { getTopTransactions } from '../lib/transactions/get_top_transactions'; + +const defaultErrorHandler = (err: Error) => { + // tslint:disable-next-line + console.error(err.stack); + throw Boom.boomify(err, { statusCode: 400 }); +}; + +export function initTransactionGroupsApi(server: Server) { + server.route({ + method: 'GET', + path: + '/api/apm/services/{serviceName}/transaction_groups/{transactionType}', + options: { + validate: { + query: withDefaultValidators({ + query: Joi.string() + }) + } + }, + handler: req => { + const { serviceName, transactionType } = req.params; + const setup = setupRequest(req); + + return getTopTransactions({ + serviceName, + transactionType, + setup + }).catch(defaultErrorHandler); + } + }); + + server.route({ + method: 'GET', + path: `/api/apm/services/{serviceName}/transaction_groups/{transactionType}/charts`, + options: { + validate: { + query: withDefaultValidators() + } + }, + handler: req => { + const setup = setupRequest(req); + const { serviceName, transactionType } = req.params; + + return getChartsData({ + serviceName, + transactionType, + setup + }).catch(defaultErrorHandler); + } + }); + + server.route({ + method: 'GET', + path: `/api/apm/services/{serviceName}/transaction_groups/{transactionType}/{transactionName}/charts`, + options: { + validate: { + query: withDefaultValidators() + } + }, + handler: req => { + const setup = setupRequest(req); + const { serviceName, transactionType, transactionName } = req.params; + + return getChartsData({ + serviceName, + transactionType, + transactionName, + setup + }).catch(defaultErrorHandler); + } + }); + + server.route({ + method: 'GET', + path: `/api/apm/services/{serviceName}/transaction_groups/{transactionType}/{transactionName}/distribution`, + options: { + validate: { + query: withDefaultValidators({ + transactionId: Joi.string().default('') + }) + } + }, + handler: req => { + const setup = setupRequest(req); + const { serviceName, transactionType, transactionName } = req.params; + const { transactionId } = req.query as { transactionId: string }; + return getDistribution( + serviceName, + transactionName, + transactionType, + transactionId, + setup + ).catch(defaultErrorHandler); + } + }); +} diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index f891d2b93734..1b1bd12f8bce 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -9,13 +9,9 @@ import { Server } from 'hapi'; import Joi from 'joi'; import { withDefaultValidators } from '../lib/helpers/input_validation'; import { setupRequest } from '../lib/helpers/setup_request'; -import { getChartsData } from '../lib/transactions/charts'; -import { getDistribution } from '../lib/transactions/distribution'; -import { getTopTransactions } from '../lib/transactions/get_top_transactions'; import { getTransaction } from '../lib/transactions/get_transaction'; import { getSpans } from '../lib/transactions/spans/get_spans'; -const ROOT = '/api/apm/services/{serviceName}/transactions'; const defaultErrorHandler = (err: Error) => { // tslint:disable-next-line console.error(err.stack); @@ -25,37 +21,11 @@ const defaultErrorHandler = (err: Error) => { export function initTransactionsApi(server: Server) { server.route({ method: 'GET', - path: ROOT, + path: `/api/apm/services/{serviceName}/transactions/{transactionId}`, options: { validate: { query: withDefaultValidators({ - transaction_type: Joi.string().default('request'), - query: Joi.string() - }) - } - }, - handler: req => { - const { serviceName } = req.params; - const { transaction_type: transactionType } = req.query as { - transaction_type: string; - }; - const setup = setupRequest(req); - - return getTopTransactions({ - serviceName, - transactionType, - setup - }).catch(defaultErrorHandler); - } - }); - - server.route({ - method: 'GET', - path: `${ROOT}/{transactionId}`, - options: { - validate: { - query: withDefaultValidators({ - traceId: Joi.string().allow('') + traceId: Joi.string().allow('') // TODO: this should be a path param and made required by 7.0 }) } }, @@ -69,9 +39,10 @@ export function initTransactionsApi(server: Server) { } }); + // TODO: this can be removed by 7.0 when v1 compatability can be dropped server.route({ method: 'GET', - path: `${ROOT}/{transactionId}/spans`, + path: `/api/apm/services/{serviceName}/transactions/{transactionId}/spans`, options: { validate: { query: withDefaultValidators() @@ -83,65 +54,4 @@ export function initTransactionsApi(server: Server) { return getSpans(transactionId, setup).catch(defaultErrorHandler); } }); - - server.route({ - method: 'GET', - path: `${ROOT}/charts`, - options: { - validate: { - query: withDefaultValidators({ - transaction_type: Joi.string().default('request'), - transaction_name: Joi.string(), - query: Joi.string() - }) - } - }, - handler: req => { - const setup = setupRequest(req); - const { serviceName } = req.params; - const { transaction_type: transactionType } = req.query as { - transaction_type: string; - }; - const { transaction_name: transactionName } = req.query as { - transaction_name: string; - }; - - return getChartsData({ - serviceName, - transactionType, - transactionName, - setup - }).catch(defaultErrorHandler); - } - }); - - server.route({ - method: 'GET', - path: `${ROOT}/distribution`, - options: { - validate: { - query: withDefaultValidators({ - transaction_name: Joi.string().required(), - transaction_id: Joi.string().default('') - }) - } - }, - handler: req => { - const setup = setupRequest(req); - const { serviceName } = req.params; - const { - transaction_name: transactionName, - transaction_id: transactionId - } = req.query as { - transaction_name: string; - transaction_id: string; - }; - return getDistribution( - serviceName, - transactionName, - transactionId, - setup - ).catch(defaultErrorHandler); - } - }); }