From cd13ccb805ece505c422549f4cdc1f0218972d73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Wed, 31 Oct 2018 13:17:32 +0100 Subject: [PATCH] [APM] Hoist loading of waterfall and flatten it (#24651) (#24893) * [APM] Hoist loading of waterfall and flatten it Remove unused test Convert to typescript * Address feedback * Make `totalDuration` optional * Renamed rootTransaction to traceRoot * [APM] Only show relevant service legends * Adds services label to the service legend * [APM] Clock skew fix Only skew child spans from the same service Take parent skew into account when finding diff # Conflicts: # x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap # x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts # x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts * Use switch statement --- .../app/ErrorGroupDetails/DetailView/index.js | 1 - .../components/app/TraceOverview/index.tsx | 1 - .../components/app/TraceOverview/view.tsx | 4 +- .../StickyTransactionProperties.tsx | 28 +- .../TransactionPropertiesTable.tsx | 14 +- .../WaterfallContainer/ServiceLegends.tsx | 6 +- .../Waterfall/FlyoutTopLevelProperties.tsx | 7 +- .../SpanFlyout/StickySpanProperties.tsx | 7 +- .../Waterfall/SpanFlyout/index.tsx | 6 +- .../Waterfall/TransactionFlyout/index.tsx | 5 +- .../Waterfall/WaterfallItem.tsx | 8 +- .../WaterfallContainer/Waterfall/index.tsx | 39 +- .../waterfall_helpers.test.ts.snap | 337 ++++-------------- .../waterfall_helpers.test.ts | 165 +++++---- .../waterfall_helpers/waterfall_helpers.ts | 186 ++++++---- .../Transaction/WaterfallContainer/index.tsx | 107 ++---- .../TransactionDetails/Transaction/index.tsx | 37 +- .../app/TransactionDetails/index.ts | 27 +- .../app/TransactionDetails/view.tsx | 33 +- .../components/shared/TransactionLink.tsx | 2 +- .../shared/charts/Timeline/index.js | 2 +- .../plugins/apm/public/services/rest/apm.ts | 5 +- ...ctionDetails.js => transactionDetails.tsx} | 18 +- .../transactionDistribution.tsx | 10 +- .../store/reactReduxRequest/waterfall.tsx | 40 +++ .../store/reactReduxRequest/waterfallV1.tsx | 15 +- .../store/reactReduxRequest/waterfallV2.tsx | 14 +- .../apm/public/store/selectors/waterfall.ts | 47 --- .../apm/server/lib/traces/get_trace.ts | 18 +- .../apm/typings/react-redux-request.d.ts | 4 +- x-pack/plugins/apm/typings/waterfall.ts | 5 +- 31 files changed, 522 insertions(+), 676 deletions(-) rename x-pack/plugins/apm/public/store/reactReduxRequest/{transactionDetails.js => transactionDetails.tsx} (68%) create mode 100644 x-pack/plugins/apm/public/store/reactReduxRequest/waterfall.tsx delete mode 100644 x-pack/plugins/apm/public/store/selectors/waterfall.ts diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.js b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.js index 27825e0d44f8..2c501f2f459c 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.js +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.js @@ -16,7 +16,6 @@ import { } from '../../../../style/variables'; import { get, capitalize, isEmpty } from 'lodash'; import { STATUS } from '../../../../constants'; - import { StickyProperties } from '../../../shared/StickyProperties'; import { Tab, HeaderMedium } from '../../../shared/UIComponents'; import DiscoverButton from '../../../shared/DiscoverButton'; diff --git a/x-pack/plugins/apm/public/components/app/TraceOverview/index.tsx b/x-pack/plugins/apm/public/components/app/TraceOverview/index.tsx index 09239e7ec6c1..b924c69eec0c 100644 --- a/x-pack/plugins/apm/public/components/app/TraceOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceOverview/index.tsx @@ -6,7 +6,6 @@ import { connect } from 'react-redux'; import { IReduxState } from '../../../store/rootReducer'; -// @ts-ignore import { getUrlParams } from '../../../store/urlParams'; import { TraceOverview as View } from './view'; diff --git a/x-pack/plugins/apm/public/components/app/TraceOverview/view.tsx b/x-pack/plugins/apm/public/components/app/TraceOverview/view.tsx index bd6dcc1068ae..3d14db033bd4 100644 --- a/x-pack/plugins/apm/public/components/app/TraceOverview/view.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceOverview/view.tsx @@ -6,7 +6,7 @@ import { EuiCallOut, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; import React from 'react'; -import { RRRRenderArgs } from 'react-redux-request'; +import { RRRRenderResponse } from 'react-redux-request'; import { ITransactionGroup } from '../../../../typings/TransactionGroup'; // @ts-ignore import { TraceListRequest } from '../../../store/reactReduxRequest/traceList'; @@ -37,7 +37,7 @@ export function TraceOverview(props: Props) { ) => ( + render={({ data, status }: RRRRenderResponse) => ( ; } - -const mapStateToProps = (state: any, props: Partial) => ({ - root: selectWaterfallRoot(state, props) -}); - -export const StickyTransactionProperties = connect<{}, {}, Props>( - mapStateToProps -)(StickyTransactionPropertiesComponent); diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionPropertiesTable.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionPropertiesTable.tsx index dff9b2979782..2a488985aaca 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionPropertiesTable.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionPropertiesTable.tsx @@ -23,6 +23,7 @@ import { PropertiesTable } from '../../../shared/PropertiesTable'; import { WaterfallContainer } from './WaterfallContainer'; +import { IWaterfall } from './WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers'; const TableContainer = styled.div` padding: ${px(units.plus)} ${px(units.plus)} 0; @@ -44,11 +45,15 @@ interface TransactionPropertiesTableProps { location: any; transaction: Transaction; urlParams: IUrlParams; + waterfall: IWaterfall; } -export const TransactionPropertiesTable: React.SFC< - TransactionPropertiesTableProps -> = ({ location, transaction, urlParams }) => { +export function TransactionPropertiesTable({ + location, + transaction, + urlParams, + waterfall +}: TransactionPropertiesTableProps) { const tabs = getTabs(transaction); const currentTab = getCurrentTab(tabs, urlParams.detailTab); const agentName = transaction.context.service.agent.name; @@ -84,6 +89,7 @@ export const TransactionPropertiesTable: React.SFC< transaction={transaction} location={location} urlParams={urlParams} + waterfall={waterfall} /> )} @@ -98,4 +104,4 @@ export const TransactionPropertiesTable: React.SFC< )} ); -}; +} diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/ServiceLegends.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/ServiceLegends.tsx index 0ebadbe099e0..f511c9d93fec 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/ServiceLegends.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/ServiceLegends.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiTitle } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; import { px, unit } from '../../../../../style/variables'; @@ -13,7 +14,7 @@ import Legend from '../../../../shared/charts/Legend'; const Legends = styled.div` display: flex; - div { + > * { margin-right: ${px(unit)}; &:last-child { margin-right: 0; @@ -30,6 +31,9 @@ interface Props { export function ServiceLegends({ serviceColors }: Props) { return ( + + Services + {Object.entries(serviceColors).map(([label, color]) => ( ))} diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/FlyoutTopLevelProperties.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/FlyoutTopLevelProperties.tsx index 77d0832e0964..dc26045151f1 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/FlyoutTopLevelProperties.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/FlyoutTopLevelProperties.tsx @@ -9,17 +9,20 @@ import { SERVICE_NAME, TRANSACTION_NAME } from 'x-pack/plugins/apm/common/constants'; -// @ts-ignore import { StickyProperties } from 'x-pack/plugins/apm/public/components/shared/StickyProperties'; import { TransactionLink } from 'x-pack/plugins/apm/public/components/shared/TransactionLink'; import { KibanaLink } from 'x-pack/plugins/apm/public/utils/url'; import { Transaction } from 'x-pack/plugins/apm/typings/Transaction'; interface Props { - transaction: Transaction; + transaction?: Transaction; } export function FlyoutTopLevelProperties({ transaction }: Props) { + if (!transaction) { + return null; + } + const stickyProperties = [ { label: 'Service', diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/SpanFlyout/StickySpanProperties.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/SpanFlyout/StickySpanProperties.tsx index c34daec0dd06..efb3890a9549 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/SpanFlyout/StickySpanProperties.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/SpanFlyout/StickySpanProperties.tsx @@ -12,7 +12,6 @@ import { first } from 'lodash'; import { Span } from '../../../../../../../../typings/Span'; // @ts-ignore import { asMillis } from '../../../../../../../utils/formatters'; -// @ts-ignore import { StickyProperties } from '../../../../../../shared/StickyProperties'; function getSpanLabel(type: string) { @@ -32,10 +31,14 @@ function getPrimaryType(type: string) { interface Props { span: Span; - totalDuration: number; + totalDuration?: number; } export function StickySpanProperties({ span, totalDuration }: Props) { + if (!totalDuration) { + return null; + } + const spanName = span.span.name; const spanDuration = span.span.duration.us; const relativeDuration = spanDuration / totalDuration; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/SpanFlyout/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/SpanFlyout/index.tsx index f5ceef5399a5..8d34ea5e2616 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/SpanFlyout/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/SpanFlyout/index.tsx @@ -55,8 +55,8 @@ function getDiscoverQuery(span: Span) { interface Props { span?: Span; - parentTransaction: Transaction; - totalDuration: number; + parentTransaction?: Transaction; + totalDuration?: number; onClose: () => void; } @@ -70,7 +70,7 @@ export function SpanFlyout({ return null; } const stackframes = span.span.stacktrace; - const codeLanguage = get(span, SERVICE_LANGUAGE_NAME); + const codeLanguage: string = get(span, SERVICE_LANGUAGE_NAME); const dbContext = span.context.db; return ( diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx index 350962e0c345..4458f71f311b 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx @@ -66,7 +66,10 @@ export function TransactionFlyout({ - + { }); }; - public renderWaterfall = (item?: IWaterfallItem) => { - if (!item) { - return null; - } - + public getWaterfallItem = (item: IWaterfallItem) => { const { serviceColors, waterfall, urlParams }: Props = this.props; return ( - - this.onOpenFlyout(item)} - /> - - {item.children && item.children.map(this.renderWaterfall)} - + this.onOpenFlyout(item)} + /> ); }; @@ -98,11 +91,15 @@ export class Waterfall extends Component { switch (currentItem.docType) { case 'span': + const parentTransaction = waterfall.getTransactionById( + currentItem.parentId + ); + return ( ); @@ -124,7 +121,7 @@ export class Waterfall extends Component { public render() { const { waterfall } = this.props; const itemContainerHeight = 58; // TODO: This is a nasty way to calculate the height of the svg element. A better approach should be found - const waterfallHeight = itemContainerHeight * waterfall.childrenCount; + const waterfallHeight = itemContainerHeight * waterfall.items.length; return ( @@ -140,7 +137,7 @@ export class Waterfall extends Component { paddingTop: TIMELINE_MARGINS.top }} > - {this.renderWaterfall(waterfall.root)} + {waterfall.items.map(this.getWaterfallItem)} diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap index 2373e1aab64a..9fd18e285d0d 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap @@ -1,265 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`getWaterfallRoot 1`] = ` -Object { - "itemsById": Object { - "a": Object { - "children": Array [ - Object { - "children": Array [], - "docType": "span", - "duration": 4694, - "id": "b2", - "name": "GET [0:0:0:0:0:0:0:1]", - "offset": 1000, - "parentId": "a", - "parentTransaction": Object {}, - "serviceName": "opbeans-java", - "span": Object { - "transaction": Object { - "id": "a", - }, - }, - "timestamp": 1536763736367000, - }, - Object { - "children": Array [ - Object { - "children": Array [ - Object { - "children": Array [], - "docType": "span", - "duration": 210, - "id": "d", - "name": "SELECT", - "offset": 5000, - "parentId": "c", - "parentTransaction": Object {}, - "serviceName": "opbeans-java", - "span": Object { - "transaction": Object { - "id": "c", - }, - }, - "timestamp": 1536763736371000, - }, - ], - "docType": "transaction", - "duration": 3581, - "id": "c", - "name": "APIRestController#productsRemote", - "offset": 3000, - "parentId": "b", - "serviceName": "opbeans-java", - "timestamp": 1536763736369000, - "transaction": Object {}, - }, - ], - "docType": "span", - "duration": 4694, - "id": "b", - "name": "GET [0:0:0:0:0:0:0:1]", - "offset": 2000, - "parentId": "a", - "parentTransaction": Object {}, - "serviceName": "opbeans-java", - "span": Object { - "transaction": Object { - "id": "a", - }, - }, - "timestamp": 1536763736368000, - }, - ], - "docType": "transaction", - "duration": 9480, - "id": "a", - "name": "APIRestController#products", - "offset": 0, - "serviceName": "opbeans-java", - "timestamp": 1536763736366000, - "transaction": Object {}, - }, - "b": Object { - "children": Array [ - Object { - "children": Array [ - Object { - "children": Array [], - "docType": "span", - "duration": 210, - "id": "d", - "name": "SELECT", - "offset": 5000, - "parentId": "c", - "parentTransaction": Object {}, - "serviceName": "opbeans-java", - "span": Object { - "transaction": Object { - "id": "c", - }, - }, - "timestamp": 1536763736371000, - }, - ], - "docType": "transaction", - "duration": 3581, - "id": "c", - "name": "APIRestController#productsRemote", - "offset": 3000, - "parentId": "b", - "serviceName": "opbeans-java", - "timestamp": 1536763736369000, - "transaction": Object {}, - }, - ], - "docType": "span", - "duration": 4694, - "id": "b", - "name": "GET [0:0:0:0:0:0:0:1]", - "offset": 2000, - "parentId": "a", - "parentTransaction": Object {}, - "serviceName": "opbeans-java", - "span": Object { - "transaction": Object { - "id": "a", - }, - }, - "timestamp": 1536763736368000, - }, - "b2": Object { - "children": Array [], - "docType": "span", - "duration": 4694, - "id": "b2", - "name": "GET [0:0:0:0:0:0:0:1]", - "offset": 1000, - "parentId": "a", - "parentTransaction": Object {}, - "serviceName": "opbeans-java", - "span": Object { - "transaction": Object { - "id": "a", - }, - }, - "timestamp": 1536763736367000, - }, - "c": Object { - "children": Array [ - Object { - "children": Array [], - "docType": "span", - "duration": 210, - "id": "d", - "name": "SELECT", - "offset": 5000, - "parentId": "c", - "parentTransaction": Object {}, - "serviceName": "opbeans-java", - "span": Object { - "transaction": Object { - "id": "c", - }, - }, - "timestamp": 1536763736371000, - }, - ], - "docType": "transaction", - "duration": 3581, - "id": "c", - "name": "APIRestController#productsRemote", - "offset": 3000, - "parentId": "b", - "serviceName": "opbeans-java", - "timestamp": 1536763736369000, - "transaction": Object {}, - }, - "d": Object { - "children": Array [], - "docType": "span", - "duration": 210, - "id": "d", - "name": "SELECT", - "offset": 5000, - "parentId": "c", - "parentTransaction": Object {}, - "serviceName": "opbeans-java", - "span": Object { - "transaction": Object { - "id": "c", - }, - }, - "timestamp": 1536763736371000, - }, - }, - "root": Object { - "children": Array [ - Object { - "children": Array [], - "docType": "span", - "duration": 4694, - "id": "b2", - "name": "GET [0:0:0:0:0:0:0:1]", - "offset": 1000, - "parentId": "a", - "parentTransaction": Object {}, - "serviceName": "opbeans-java", - "span": Object { - "transaction": Object { - "id": "a", - }, - }, - "timestamp": 1536763736367000, - }, - Object { - "children": Array [ - Object { - "children": Array [ - Object { - "children": Array [], - "docType": "span", - "duration": 210, - "id": "d", - "name": "SELECT", - "offset": 5000, - "parentId": "c", - "parentTransaction": Object {}, - "serviceName": "opbeans-java", - "span": Object { - "transaction": Object { - "id": "c", - }, - }, - "timestamp": 1536763736371000, - }, - ], - "docType": "transaction", - "duration": 3581, - "id": "c", - "name": "APIRestController#productsRemote", - "offset": 3000, - "parentId": "b", - "serviceName": "opbeans-java", - "timestamp": 1536763736369000, - "transaction": Object {}, - }, - ], - "docType": "span", - "duration": 4694, - "id": "b", - "name": "GET [0:0:0:0:0:0:0:1]", - "offset": 2000, - "parentId": "a", - "parentTransaction": Object {}, - "serviceName": "opbeans-java", - "span": Object { - "transaction": Object { - "id": "a", - }, - }, - "timestamp": 1536763736368000, - }, +exports[`waterfall_helpers getWaterfallItems should order items correctly 1`] = ` +Array [ + Object { + "childIds": Array [ + "b2", + "b", ], "docType": "transaction", "duration": 9480, @@ -267,8 +13,77 @@ Object { "name": "APIRestController#products", "offset": 0, "serviceName": "opbeans-java", + "skew": 0, "timestamp": 1536763736366000, "transaction": Object {}, }, -} + Object { + "childIds": Array [], + "docType": "span", + "duration": 4694, + "id": "b2", + "name": "GET [0:0:0:0:0:0:0:1]", + "offset": 1000, + "parentId": "a", + "serviceName": "opbeans-java", + "skew": 0, + "span": Object { + "transaction": Object { + "id": "a", + }, + }, + "timestamp": 1536763736367000, + }, + Object { + "childIds": Array [ + "c", + ], + "docType": "span", + "duration": 4694, + "id": "b", + "name": "GET [0:0:0:0:0:0:0:1]", + "offset": 2000, + "parentId": "a", + "serviceName": "opbeans-java", + "skew": 0, + "span": Object { + "transaction": Object { + "id": "a", + }, + }, + "timestamp": 1536763736368000, + }, + Object { + "childIds": Array [ + "d", + ], + "docType": "transaction", + "duration": 3581, + "id": "c", + "name": "APIRestController#productsRemote", + "offset": 3000, + "parentId": "b", + "serviceName": "opbeans-java", + "skew": 0, + "timestamp": 1536763736369000, + "transaction": Object {}, + }, + Object { + "childIds": Array [], + "docType": "span", + "duration": 210, + "id": "d", + "name": "SELECT", + "offset": 5000, + "parentId": "c", + "serviceName": "opbeans-java", + "skew": 0, + "span": Object { + "transaction": Object { + "id": "c", + }, + }, + "timestamp": 1536763736371000, + }, +] `; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts index 92305031c5af..3669f6a378a7 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts @@ -4,82 +4,101 @@ * you may not use this file except in compliance with the Elastic License. */ +import { groupBy, indexBy } from 'lodash'; import { Span } from 'x-pack/plugins/apm/typings/Span'; import { Transaction } from 'x-pack/plugins/apm/typings/Transaction'; -import { getWaterfallRoot, IWaterfallItem } from './waterfall_helpers'; +import { + getWaterfallItems, + IWaterfallIndex, + IWaterfallItem +} from './waterfall_helpers'; -it('getWaterfallRoot', () => { - const items: IWaterfallItem[] = [ - { - id: 'd', - parentId: 'c', - serviceName: 'opbeans-java', - name: 'SELECT', - duration: 210, - timestamp: 1536763736371000, - offset: 0, - docType: 'span', - parentTransaction: {} as Transaction, - span: { - transaction: { - id: 'c' +describe('waterfall_helpers', () => { + describe('getWaterfallItems', () => { + it('should order items correctly', () => { + const items: IWaterfallItem[] = [ + { + id: 'd', + parentId: 'c', + serviceName: 'opbeans-java', + name: 'SELECT', + duration: 210, + timestamp: 1536763736371000, + offset: 0, + skew: 0, + docType: 'span', + span: { + transaction: { + id: 'c' + } + } as Span + }, + { + id: 'b', + parentId: 'a', + serviceName: 'opbeans-java', + name: 'GET [0:0:0:0:0:0:0:1]', + duration: 4694, + timestamp: 1536763736368000, + offset: 0, + skew: 0, + docType: 'span', + span: { + transaction: { + id: 'a' + } + } as Span + }, + { + id: 'b2', + parentId: 'a', + serviceName: 'opbeans-java', + name: 'GET [0:0:0:0:0:0:0:1]', + duration: 4694, + timestamp: 1536763736367000, + offset: 0, + skew: 0, + docType: 'span', + span: { + transaction: { + id: 'a' + } + } as Span + }, + { + id: 'c', + parentId: 'b', + serviceName: 'opbeans-java', + name: 'APIRestController#productsRemote', + duration: 3581, + timestamp: 1536763736369000, + offset: 0, + skew: 0, + docType: 'transaction', + transaction: {} as Transaction + }, + { + id: 'a', + serviceName: 'opbeans-java', + name: 'APIRestController#products', + duration: 9480, + timestamp: 1536763736366000, + offset: 0, + skew: 0, + docType: 'transaction', + transaction: {} as Transaction } - } as Span - }, - { - id: 'b', - parentId: 'a', - serviceName: 'opbeans-java', - name: 'GET [0:0:0:0:0:0:0:1]', - duration: 4694, - timestamp: 1536763736368000, - offset: 0, - docType: 'span', - parentTransaction: {} as Transaction, - span: { - transaction: { - id: 'a' - } - } as Span - }, - { - id: 'b2', - parentId: 'a', - serviceName: 'opbeans-java', - name: 'GET [0:0:0:0:0:0:0:1]', - duration: 4694, - timestamp: 1536763736367000, - offset: 0, - docType: 'span', - parentTransaction: {} as Transaction, - span: { - transaction: { - id: 'a' - } - } as Span - }, - { - id: 'c', - parentId: 'b', - serviceName: 'opbeans-java', - name: 'APIRestController#productsRemote', - duration: 3581, - timestamp: 1536763736369000, - offset: 0, - docType: 'transaction', - transaction: {} as Transaction - }, - { - id: 'a', - serviceName: 'opbeans-java', - name: 'APIRestController#products', - duration: 9480, - timestamp: 1536763736366000, - offset: 0, - docType: 'transaction', - transaction: {} as Transaction - } - ]; + ]; - expect(getWaterfallRoot(items, items[4])).toMatchSnapshot(); + const childrenByParentId = groupBy( + items, + hit => (hit.parentId ? hit.parentId : 'root') + ); + const itemsById: IWaterfallIndex = indexBy(items, 'id'); + const entryTransactionItem = childrenByParentId.root[0]; + expect( + getWaterfallItems(childrenByParentId, itemsById, entryTransactionItem) + ).toMatchSnapshot(); + }); + }); }); diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts index 3b036da8202e..739f360a694c 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts @@ -4,7 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { groupBy, indexBy, sortBy } from 'lodash'; +import { + first, + flatten, + groupBy, + indexBy, + isEmpty, + sortBy, + uniq +} from 'lodash'; import { Span } from '../../../../../../../../typings/Span'; import { Transaction } from '../../../../../../../../typings/Transaction'; @@ -12,12 +20,18 @@ export interface IWaterfallIndex { [key: string]: IWaterfallItem; } +export interface IWaterfallGroup { + [key: string]: IWaterfallItem[]; +} + export interface IWaterfall { - duration: number; + traceRoot?: Transaction; + traceRootDuration?: number; + duration?: number; services: string[]; - childrenCount: number; - root: IWaterfallItem; + items: IWaterfallItem[]; itemsById: IWaterfallIndex; + getTransactionById: (id?: IWaterfallItem['id']) => Transaction | undefined; } interface IWaterfallItemBase { @@ -28,25 +42,22 @@ interface IWaterfallItemBase { duration: number; timestamp: number; offset: number; + skew: number; + childIds?: Array; } interface IWaterfallItemTransaction extends IWaterfallItemBase { transaction: Transaction; docType: 'transaction'; - children?: Array; } interface IWaterfallItemSpan extends IWaterfallItemBase { - parentTransaction: Transaction; span: Span; docType: 'span'; - children?: Array; } export type IWaterfallItem = IWaterfallItemSpan | IWaterfallItemTransaction; -type Omit = Pick>; - function getTransactionItem( transaction: Transaction ): IWaterfallItemTransaction { @@ -58,6 +69,7 @@ function getTransactionItem( duration: transaction.transaction.duration.us, timestamp: new Date(transaction['@timestamp']).getTime() * 1000, offset: 0, + skew: 0, docType: 'transaction', transaction }; @@ -71,14 +83,13 @@ function getTransactionItem( duration: transaction.transaction.duration.us, timestamp: transaction.timestamp.us, offset: 0, + skew: 0, docType: 'transaction', transaction }; } -type PartialSpanItem = Omit; - -function getSpanItem(span: Span): PartialSpanItem { +function getSpanItem(span: Span): IWaterfallItemSpan { if (span.version === 'v1') { return { id: span.span.id, @@ -89,6 +100,7 @@ function getSpanItem(span: Span): PartialSpanItem { timestamp: new Date(span['@timestamp']).getTime() * 1000 + span.span.start.us, offset: 0, + skew: 0, docType: 'span', span }; @@ -102,66 +114,94 @@ function getSpanItem(span: Span): PartialSpanItem { duration: span.span.duration.us, timestamp: span.timestamp.us, offset: 0, + skew: 0, docType: 'span', span }; } -export function getWaterfallRoot( - items: Array, - entryTransactionItem: IWaterfallItem +function getClockSkew( + item: IWaterfallItem, + itemsById: IWaterfallIndex, + parentTransactionSkew: number ) { - const itemsByParentId = groupBy( - items, - item => (item.parentId ? item.parentId : 'root') - ); - const itemsById: IWaterfallIndex = {}; - - const itemsByTransactionId = indexBy( - items.filter(item => item.docType === 'transaction'), - item => item.id - ) as { [key: string]: IWaterfallItemTransaction }; - - function getWithChildren( - item: PartialSpanItem | IWaterfallItemTransaction - ): IWaterfallItem { - const children = itemsByParentId[item.id] || []; - const nextChildren = sortBy(children, 'timestamp').map(getWithChildren); - let fullItem; - - // add parent transaction to spans - if (item.docType === 'span') { - fullItem = { - parentTransaction: - itemsByTransactionId[item.span.transaction.id].transaction, - ...item, - offset: item.timestamp - entryTransactionItem.timestamp, - children: nextChildren - }; - } else { - fullItem = { - ...item, - offset: item.timestamp - entryTransactionItem.timestamp, - children: nextChildren - }; + switch (item.docType) { + case 'span': + return parentTransactionSkew; + case 'transaction': { + if (!item.parentId) { + return 0; + } + + const parentItem = itemsById[item.parentId]; + + // determine if child starts before the parent, and in that case how much + const diff = parentItem.timestamp + parentItem.skew - item.timestamp; + + // If child transaction starts after parent span there is no clock skew + if (diff < 0) { + return 0; + } + + // latency can only be calculated if parent duration is larger than child duration + const latency = Math.max(parentItem.duration - item.duration, 0); + const skew = diff + latency / 2; + return skew; } + } +} - // TODO: Think about storing this tree as a single, flat, indexed structure - // with "children" being an array of ids, instead of it being a real tree - itemsById[item.id] = fullItem; +export function getWaterfallItems( + childrenByParentId: IWaterfallGroup, + itemsById: IWaterfallIndex, + entryTransactionItem: IWaterfallItem +) { + function getSortedChildren( + item: IWaterfallItem, + parentTransactionSkew: number + ): IWaterfallItem[] { + const skew = getClockSkew(item, itemsById, parentTransactionSkew); + const children = sortBy(childrenByParentId[item.id] || [], 'timestamp'); + + item.childIds = children.map(child => child.id); + item.offset = item.timestamp - entryTransactionItem.timestamp; + item.skew = skew; + + const deepChildren = flatten( + children.map(child => getSortedChildren(child, skew)) + ); + return [item, ...deepChildren]; + } + + return getSortedChildren(entryTransactionItem, 0); +} - return fullItem; +function getTraceRoot(childrenByParentId: IWaterfallGroup) { + const item = first(childrenByParentId.root); + if (item && item.docType === 'transaction') { + return item.transaction; } +} - return { root: getWithChildren(entryTransactionItem), itemsById }; +function getServices(items: IWaterfallItem[]) { + const serviceNames = items.map(item => item.serviceName); + return uniq(serviceNames); } export function getWaterfall( hits: Array, - services: string[], entryTransaction: Transaction ): IWaterfall { - const items = hits + if (isEmpty(hits)) { + return { + services: [], + items: [], + itemsById: {}, + getTransactionById: () => undefined + }; + } + + const filteredHits = hits .filter(hit => { const docType = hit.processor.event; return ['span', 'transaction'].includes(docType); @@ -178,13 +218,37 @@ export function getWaterfall( } }); + const childrenByParentId = groupBy( + filteredHits, + hit => (hit.parentId ? hit.parentId : 'root') + ); const entryTransactionItem = getTransactionItem(entryTransaction); - const { root, itemsById } = getWaterfallRoot(items, entryTransactionItem); + const itemsById: IWaterfallIndex = indexBy(filteredHits, 'id'); + const items = getWaterfallItems( + childrenByParentId, + itemsById, + entryTransactionItem + ); + const traceRoot = getTraceRoot(childrenByParentId); + + const getTransactionById = (id?: IWaterfallItem['id']) => { + if (!id) { + return; + } + + const item = itemsById[id]; + if (item.docType === 'transaction') { + return item.transaction; + } + }; + return { - duration: root.duration, - services, - childrenCount: hits.length, - root, - itemsById + traceRoot, + traceRootDuration: traceRoot && traceRoot.transaction.duration.us, + duration: entryTransaction.transaction.duration.us, + services: getServices(items), + items, + itemsById, + getTransactionById }; } diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/index.tsx index ebdd98abd4d3..a48f944b8f2c 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/index.tsx @@ -14,96 +14,55 @@ import { import { Transaction } from '../../../../../../typings/Transaction'; import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; -import { RRRRender } from 'react-redux-request'; -import { WaterfallV1Request } from 'x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV1'; -import { WaterfallV2Request } from 'x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV2'; + import { IUrlParams } from 'x-pack/plugins/apm/public/store/urlParams'; -import { WaterfallResponse } from 'x-pack/plugins/apm/typings/waterfall'; import { getAgentMarks } from './get_agent_marks'; import { getServiceColors } from './getServiceColors'; import { ServiceLegends } from './ServiceLegends'; import { Waterfall } from './Waterfall'; -import { getWaterfall } from './Waterfall/waterfall_helpers/waterfall_helpers'; +import { IWaterfall } from './Waterfall/waterfall_helpers/waterfall_helpers'; interface Props { urlParams: IUrlParams; transaction: Transaction; location: any; -} - -interface WaterfallRequestProps { - urlParams: IUrlParams; - transaction: Transaction; - render: RRRRender; -} - -function WaterfallRequest({ - urlParams, - transaction, - render -}: WaterfallRequestProps) { - const hasTrace = transaction.hasOwnProperty('trace'); - if (hasTrace) { - return ( - - ); - } else { - return ( - - ); - } + waterfall: IWaterfall; } export function WaterfallContainer({ location, urlParams, - transaction + transaction, + waterfall }: Props) { - return ( - { - const agentMarks = getAgentMarks(transaction); - const waterfall = getWaterfall(data.hits, data.services, transaction); - if (!waterfall) { - return null; - } - const serviceColors = getServiceColors(waterfall.services); + const agentMarks = getAgentMarks(transaction); + if (!waterfall) { + return null; + } + const serviceColors = getServiceColors(waterfall.services); - return ( -
- - - - - - - Beta - - - - -
- ); - }} - /> + return ( +
+ + + + + + + Beta + + + + +
); } diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/index.tsx index 94ad794ed88f..2039bb64f405 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/index.tsx @@ -22,20 +22,22 @@ import EmptyMessage from '../../../shared/EmptyMessage'; import { TransactionLink } from '../../../shared/TransactionLink'; import { DiscoverTransactionLink } from './ActionMenu'; import { StickyTransactionProperties } from './StickyTransactionProperties'; -// @ts-ignore import { TransactionPropertiesTable } from './TransactionPropertiesTable'; +import { IWaterfall } from './WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers'; function MaybeViewTraceLink({ - root, - transaction + transaction, + waterfall }: { - root: ITransaction; transaction: ITransaction; + waterfall: IWaterfall; }) { - const isRoot = transaction.transaction.id === root.transaction.id; - let button; + const isRoot = + transaction.transaction.id === + (waterfall.traceRoot && waterfall.traceRoot.transaction.id); - if (isRoot || !root) { + let button; + if (isRoot) { button = ( @@ -49,7 +51,9 @@ function MaybeViewTraceLink({ return ( - {button} + + {button} + ); } @@ -58,14 +62,14 @@ interface Props { transaction: ITransaction; urlParams: IUrlParams; location: Location; - waterfallRoot?: ITransaction; + waterfall: IWaterfall; } export const Transaction: React.SFC = ({ transaction, urlParams, location, - waterfallRoot + waterfall }) => { if (isEmpty(transaction)) { return ( @@ -76,8 +80,6 @@ export const Transaction: React.SFC = ({ ); } - const root = waterfallRoot || transaction; - return ( @@ -96,14 +98,20 @@ export const Transaction: React.SFC = ({ - + - + @@ -111,6 +119,7 @@ export const Transaction: React.SFC = ({ transaction={transaction} location={location} urlParams={urlParams} + waterfall={waterfall} /> ); diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.ts b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.ts index af2441392828..6ab8c0116337 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.ts +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.ts @@ -6,29 +6,16 @@ import { connect } from 'react-redux'; import { TransactionDetailsView } from 'x-pack/plugins/apm/public/components/app/TransactionDetails/view'; -import { selectWaterfallRoot } from 'x-pack/plugins/apm/public/store/selectors/waterfall'; -import { - getUrlParams, - IUrlParams -} from 'x-pack/plugins/apm/public/store/urlParams'; -import { Transaction } from '../../../../typings/Transaction'; +import { getUrlParams } from 'x-pack/plugins/apm/public/store/urlParams'; +import { IReduxState } from '../../../store/rootReducer'; -interface Props { - location: any; - urlParams: IUrlParams; - waterfallRoot: Transaction; -} - -function mapStateToProps(state: any = {}, props: Partial) { +function mapStateToProps(state = {} as IReduxState) { return { location: state.location, - urlParams: getUrlParams(state), - waterfallRoot: selectWaterfallRoot(state, props) + urlParams: getUrlParams(state) }; } -const mapDispatchToProps = {}; -export const TransactionDetails = connect<{}, {}, Props>( - mapStateToProps, - mapDispatchToProps -)(TransactionDetailsView); +export const TransactionDetails = connect(mapStateToProps)( + TransactionDetailsView +); diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/view.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/view.tsx index 6ddc0c85c216..0ed4f80fd353 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/view.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/view.tsx @@ -6,13 +6,13 @@ import { EuiSpacer } from '@elastic/eui'; import React from 'react'; -import { RRRRenderArgs } from 'react-redux-request'; -import { Transaction as ITransaction } from '../../../../typings/Transaction'; +import { RRRRenderResponse } from 'react-redux-request'; // @ts-ignore import { TransactionDetailsRequest } from '../../../store/reactReduxRequest/transactionDetails'; // @ts-ignore import { TransactionDetailsChartsRequest } from '../../../store/reactReduxRequest/transactionDetailsCharts'; import { TransactionDistributionRequest } from '../../../store/reactReduxRequest/transactionDistribution'; +import { WaterfallRequest } from '../../../store/reactReduxRequest/waterfall'; import { IUrlParams } from '../../../store/urlParams'; // @ts-ignore import TransactionCharts from '../../shared/charts/TransactionCharts'; @@ -26,14 +26,9 @@ import { Transaction } from './Transaction'; interface Props { urlParams: IUrlParams; location: any; - waterfallRoot: ITransaction; } -export function TransactionDetailsView({ - urlParams, - location, - waterfallRoot -}: Props) { +export function TransactionDetailsView({ urlParams, location }: Props) { return (
{urlParams.transactionName} @@ -44,7 +39,7 @@ export function TransactionDetailsView({ ) => ( + render={({ data }: RRRRenderResponse) => ( ) => ( + render={({ data }) => ( ) => { + render={({ data: transaction }) => { return ( - { + return ( + + ); + }} /> ); }} diff --git a/x-pack/plugins/apm/public/components/shared/TransactionLink.tsx b/x-pack/plugins/apm/public/components/shared/TransactionLink.tsx index a78131a4c352..eab7735b5073 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionLink.tsx @@ -9,7 +9,7 @@ import { Transaction } from '../../../typings/Transaction'; import { KibanaLink, legacyEncodeURIComponent } from '../../utils/url'; interface TransactionLinkProps { - transaction: Transaction; + transaction?: Transaction; } /** diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/index.js b/x-pack/plugins/apm/public/components/shared/charts/Timeline/index.js index b018aee081db..711007341c50 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/index.js +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/index.js @@ -32,7 +32,7 @@ class Timeline extends PureComponent { Timeline.propTypes = { agentMarks: PropTypes.array, - duration: PropTypes.number.isRequired, + duration: PropTypes.number, height: PropTypes.number.isRequired, header: PropTypes.node, margins: PropTypes.object.isRequired, diff --git a/x-pack/plugins/apm/public/services/rest/apm.ts b/x-pack/plugins/apm/public/services/rest/apm.ts index 93980cbd5218..665a9cc7dfa9 100644 --- a/x-pack/plugins/apm/public/services/rest/apm.ts +++ b/x-pack/plugins/apm/public/services/rest/apm.ts @@ -180,7 +180,7 @@ export async function loadSpans({ } export async function loadTrace({ traceId, start, end }: IUrlParams) { - const result: WaterfallResponse = await callApi( + const hits: WaterfallResponse = await callApi( { pathname: `/api/apm/traces/${traceId}`, query: { @@ -193,8 +193,7 @@ export async function loadTrace({ traceId, start, end }: IUrlParams) { } ); - result.hits = result.hits.map(addVersion); - return result; + return hits.map(addVersion); } export async function loadTransaction({ diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetails.js b/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetails.tsx similarity index 68% rename from x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetails.js rename to x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetails.tsx index 887bb2a16dcb..1bf8dc5e29c7 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetails.js +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetails.tsx @@ -5,19 +5,29 @@ */ import React from 'react'; -import { createInitialDataSelector } from './helpers'; -import { Request } from 'react-redux-request'; +import { Request, RRRRender } from 'react-redux-request'; +import { Transaction } from 'x-pack/plugins/apm/typings/Transaction'; import { loadTransaction } from '../../services/rest/apm'; +import { IReduxState } from '../rootReducer'; +import { IUrlParams } from '../urlParams'; +// @ts-ignore +import { createInitialDataSelector } from './helpers'; const ID = 'transactionDetails'; const INITIAL_DATA = {}; const withInitialData = createInitialDataSelector(INITIAL_DATA); -export function getTransactionDetails(state) { +export function getTransactionDetails(state: IReduxState) { return withInitialData(state.reactReduxRequest[ID]); } -export function TransactionDetailsRequest({ urlParams, render }) { +export function TransactionDetailsRequest({ + urlParams, + render +}: { + urlParams: IUrlParams; + render: RRRRender; +}) { const { serviceName, start, end, transactionId, traceId, kuery } = urlParams; if (!(serviceName && start && end && transactionId)) { diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDistribution.tsx b/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDistribution.tsx index 26f2d46add30..b52644d6881d 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDistribution.tsx +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDistribution.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { Request, RRRRenderArgs } from 'react-redux-request'; +import { Request, RRRRender, RRRRenderResponse } from 'react-redux-request'; import { IDistributionResponse } from '../../../server/lib/transactions/distribution/get_distribution'; import { loadTransactionDistribution } from '../../services/rest/apm'; import { IReduxState } from '../rootReducer'; @@ -17,13 +17,9 @@ const ID = 'transactionDistribution'; const INITIAL_DATA = { buckets: [], totalHits: 0 }; const withInitialData = createInitialDataSelector(INITIAL_DATA); -interface RrrResponse { - data: T; -} - export function getTransactionDistribution( state: IReduxState -): RrrResponse { +): RRRRenderResponse { return withInitialData(state.reactReduxRequest[ID]); } @@ -41,7 +37,7 @@ export function TransactionDistributionRequest({ render }: { urlParams: IUrlParams; - render: (args: RRRRenderArgs) => any; + render: RRRRender; }) { const { serviceName, start, end, transactionName, kuery } = urlParams; diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/waterfall.tsx b/x-pack/plugins/apm/public/store/reactReduxRequest/waterfall.tsx new file mode 100644 index 000000000000..13038fdea15d --- /dev/null +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/waterfall.tsx @@ -0,0 +1,40 @@ +/* + * 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 React from 'react'; +import { RRRRender } from 'react-redux-request'; +import { Transaction } from 'x-pack/plugins/apm/typings/Transaction'; +import { IWaterfall } from '../../components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers'; +import { IUrlParams } from '../urlParams'; +import { WaterfallV1Request } from './waterfallV1'; +import { WaterfallV2Request } from './waterfallV2'; + +interface Props { + urlParams: IUrlParams; + transaction: Transaction; + render: RRRRender; +} + +export function WaterfallRequest({ urlParams, transaction, render }: Props) { + const hasTrace = transaction.hasOwnProperty('trace'); + if (hasTrace) { + return ( + + ); + } else { + return ( + + ); + } +} diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV1.tsx b/x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV1.tsx index 00b6709fc501..f04f37c5f60d 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV1.tsx +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV1.tsx @@ -13,7 +13,10 @@ import { } from 'x-pack/plugins/apm/common/constants'; import { Span } from 'x-pack/plugins/apm/typings/Span'; import { Transaction } from 'x-pack/plugins/apm/typings/Transaction'; -import { WaterfallResponse } from 'x-pack/plugins/apm/typings/waterfall'; +import { + getWaterfall, + IWaterfall +} from '../../components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers'; import { loadSpans } from '../../services/rest/apm'; import { IUrlParams } from '../urlParams'; // @ts-ignore @@ -24,7 +27,7 @@ export const ID = 'waterfallV1'; interface Props { urlParams: IUrlParams; transaction: Transaction; - render: RRRRender; + render: RRRRender; } export function WaterfallV1Request({ urlParams, transaction, render }: Props) { @@ -42,12 +45,8 @@ export function WaterfallV1Request({ urlParams, transaction, render }: Props) { fn={loadSpans} args={[{ serviceName, start, end, transactionId }]} render={({ status, data = [], args }) => { - const res = { - hits: [transaction, ...data], - services: [serviceName] - }; - - return render({ status, data: res, args }); + const waterfall = getWaterfall([transaction, ...data], transaction); + return render({ status, data: waterfall, args }); }} /> ); diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV2.tsx b/x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV2.tsx index c5983f757409..43e4f65c91d4 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV2.tsx +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV2.tsx @@ -10,6 +10,10 @@ import { Request, RRRRender } from 'react-redux-request'; import { TRACE_ID } from 'x-pack/plugins/apm/common/constants'; import { Transaction } from 'x-pack/plugins/apm/typings/Transaction'; import { WaterfallResponse } from 'x-pack/plugins/apm/typings/waterfall'; +import { + getWaterfall, + IWaterfall +} from '../../components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers'; import { loadTrace } from '../../services/rest/apm'; import { IUrlParams } from '../urlParams'; // @ts-ignore @@ -20,10 +24,9 @@ export const ID = 'waterfallV2'; interface Props { urlParams: IUrlParams; transaction: Transaction; - render: RRRRender; + render: RRRRender; } -const defaultData = { hits: [], services: [] }; export function WaterfallV2Request({ urlParams, transaction, render }: Props) { const { start, end } = urlParams; const traceId: string = get(transaction, TRACE_ID); @@ -37,9 +40,10 @@ export function WaterfallV2Request({ urlParams, transaction, render }: Props) { id={ID} fn={loadTrace} args={[{ traceId, start, end }]} - render={({ args, data = defaultData, status }) => - render({ args, data, status }) - } + render={({ args, data = [], status }) => { + const waterfall = getWaterfall(data, transaction); + return render({ args, data: waterfall, status }); + }} /> ); } diff --git a/x-pack/plugins/apm/public/store/selectors/waterfall.ts b/x-pack/plugins/apm/public/store/selectors/waterfall.ts deleted file mode 100644 index 632a3c001b38..000000000000 --- a/x-pack/plugins/apm/public/store/selectors/waterfall.ts +++ /dev/null @@ -1,47 +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. - */ - -import { RRRRenderArgs } from 'react-redux-request'; -import { createSelector, ParametricSelector } from 'reselect'; -import { TransactionV2 } from '../../../typings/Transaction'; -import { WaterfallResponse } from '../../../typings/waterfall'; -import { ID as v1ID } from '../reactReduxRequest/waterfallV1'; -import { ID as v2ID } from '../reactReduxRequest/waterfallV2'; - -interface ReduxState { - reactReduxRequest: { - [v1ID]?: RRRRenderArgs; - [v2ID]?: RRRRenderArgs; - }; -} - -export const selectWaterfall: ParametricSelector< - ReduxState, - any, - WaterfallResponse | null -> = state => { - const waterfall = - state.reactReduxRequest[v1ID] || state.reactReduxRequest[v2ID]; - - if (!waterfall || !waterfall.data) { - return null; - } - - return waterfall.data; -}; - -export const selectWaterfallRoot = createSelector( - [selectWaterfall], - waterfall => { - if (!waterfall || !waterfall.hits) { - return; - } - - return waterfall.hits.find( - hit => hit.version === 'v2' && !hit.parent - ) as TransactionV2; - } -); diff --git a/x-pack/plugins/apm/server/lib/traces/get_trace.ts b/x-pack/plugins/apm/server/lib/traces/get_trace.ts index eb43e5a76185..b92e9e377e70 100644 --- a/x-pack/plugins/apm/server/lib/traces/get_trace.ts +++ b/x-pack/plugins/apm/server/lib/traces/get_trace.ts @@ -6,8 +6,7 @@ import { SearchParams, SearchResponse } from 'elasticsearch'; import { WaterfallResponse } from 'x-pack/plugins/apm/typings/waterfall'; -import { SERVICE_NAME, TRACE_ID } from '../../../common/constants'; -import { TermsAggsBucket } from '../../../typings/elasticsearch'; +import { TRACE_ID } from '../../../common/constants'; import { Span } from '../../../typings/Span'; import { Transaction } from '../../../typings/Transaction'; import { Setup } from '../helpers/setup_request'; @@ -37,14 +36,6 @@ export async function getTrace( } ] } - }, - aggs: { - services: { - terms: { - field: SERVICE_NAME, - size: 500 - } - } } } }; @@ -54,10 +45,5 @@ export async function getTrace( params ); - return { - services: (resp.aggregations.services.buckets as TermsAggsBucket[]).map( - bucket => bucket.key - ), - hits: resp.hits.hits.map(hit => hit._source) - }; + return resp.hits.hits.map(hit => hit._source); } diff --git a/x-pack/plugins/apm/typings/react-redux-request.d.ts b/x-pack/plugins/apm/typings/react-redux-request.d.ts index abee82e2f675..23a3b7578772 100644 --- a/x-pack/plugins/apm/typings/react-redux-request.d.ts +++ b/x-pack/plugins/apm/typings/react-redux-request.d.ts @@ -9,14 +9,14 @@ declare module 'react-redux-request' { import React from 'react'; - export interface RRRRenderArgs { + export interface RRRRenderResponse { status: 'SUCCESS' | 'LOADING' | 'FAILURE'; data: T; args: P; } export type RRRRender = ( - args: RRRRenderArgs + res: RRRRenderResponse ) => JSX.Element | null; export interface RequestProps { diff --git a/x-pack/plugins/apm/typings/waterfall.ts b/x-pack/plugins/apm/typings/waterfall.ts index ad191907698d..d33cdcda65cc 100644 --- a/x-pack/plugins/apm/typings/waterfall.ts +++ b/x-pack/plugins/apm/typings/waterfall.ts @@ -7,7 +7,4 @@ import { Span } from './Span'; import { Transaction } from './Transaction'; -export interface WaterfallResponse { - services: string[]; - hits: Array; -} +export type WaterfallResponse = Array;