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 27825e0d44f84..2c501f2f459c3 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/view.tsx b/x-pack/plugins/apm/public/components/app/TraceOverview/view.tsx index bd6dcc1068ae2..3d14db033bd41 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 dff9b29797821..2a488985aaca0 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/Waterfall/FlyoutTopLevelProperties.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/FlyoutTopLevelProperties.tsx index 77d0832e0964b..dc26045151f17 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 c34daec0dd06e..3bd98f36def36 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) { 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 f5ceef5399a58..af7de0f2a39ce 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,7 +55,7 @@ function getDiscoverQuery(span: Span) { interface Props { span?: Span; - parentTransaction: Transaction; + 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 4460404066fba..455a31df019df 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 @@ -61,7 +61,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 2373e1aab64a5..758c76a78e3e0 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,75 +1,92 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`getWaterfallRoot 1`] = ` +exports[`waterfall_helpers getWaterfallItems should order items correctly 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, + "items": Array [ + Object { + "childIds": Array [ + "b2", + "b", + ], + "docType": "transaction", + "duration": 9480, + "id": "a", + "name": "APIRestController#products", + "offset": 0, + "serviceName": "opbeans-java", + "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", + "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", + "span": Object { + "transaction": Object { + "id": "a", }, - 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, + }, + "timestamp": 1536763736368000, + }, + Object { + "childIds": Array [ + "d", + ], + "docType": "transaction", + "duration": 3581, + "id": "c", + "name": "APIRestController#productsRemote", + "offset": 3000, + "parentId": "b", + "serviceName": "opbeans-java", + "timestamp": 1536763736369000, + "transaction": Object {}, + }, + Object { + "childIds": Array [], + "docType": "span", + "duration": 210, + "id": "d", + "name": "SELECT", + "offset": 5000, + "parentId": "c", + "serviceName": "opbeans-java", + "span": Object { + "transaction": Object { + "id": "c", }, + }, + "timestamp": 1536763736371000, + }, + ], + "itemsById": Object { + "a": Object { + "childIds": Array [ + "b2", + "b", ], "docType": "transaction", "duration": 9480, @@ -81,37 +98,8 @@ Object { "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 {}, - }, + "childIds": Array [ + "c", ], "docType": "span", "duration": 4694, @@ -119,7 +107,6 @@ Object { "name": "GET [0:0:0:0:0:0:0:1]", "offset": 2000, "parentId": "a", - "parentTransaction": Object {}, "serviceName": "opbeans-java", "span": Object { "transaction": Object { @@ -129,14 +116,13 @@ Object { "timestamp": 1536763736368000, }, "b2": Object { - "children": Array [], + "childIds": 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 { @@ -146,24 +132,8 @@ Object { "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, - }, + "childIds": Array [ + "d", ], "docType": "transaction", "duration": 3581, @@ -176,14 +146,13 @@ Object { "transaction": Object {}, }, "d": Object { - "children": Array [], + "childIds": Array [], "docType": "span", "duration": 210, "id": "d", "name": "SELECT", "offset": 5000, "parentId": "c", - "parentTransaction": Object {}, "serviceName": "opbeans-java", "span": Object { "transaction": Object { @@ -193,82 +162,5 @@ Object { "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, - }, - ], - "docType": "transaction", - "duration": 9480, - "id": "a", - "name": "APIRestController#products", - "offset": 0, - "serviceName": "opbeans-java", - "timestamp": 1536763736366000, - "transaction": Object {}, - }, } `; 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 92305031c5af6..256d9796e7e11 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 @@ -6,80 +6,82 @@ 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, 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, + 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, + 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, + docType: 'span', + 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 } - } 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 entryTransactionItem = items[4]; + expect(getWaterfallItems(items, 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 3b036da8202ec..4a403ea4869e7 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,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { groupBy, indexBy, sortBy } from 'lodash'; +import { flatten, groupBy, indexBy, sortBy } from 'lodash'; import { Span } from '../../../../../../../../typings/Span'; import { Transaction } from '../../../../../../../../typings/Transaction'; @@ -13,11 +13,13 @@ export interface IWaterfallIndex { } export interface IWaterfall { + rootTransaction?: Transaction; + rootTransactionDuration?: number; duration: number; services: string[]; - childrenCount: number; - root: IWaterfallItem; + items: IWaterfallItem[]; itemsById: IWaterfallIndex; + getTransactionById: (id?: IWaterfallItem['id']) => Transaction | undefined; } interface IWaterfallItemBase { @@ -28,25 +30,21 @@ interface IWaterfallItemBase { duration: number; timestamp: number; offset: 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 { @@ -76,9 +74,7 @@ function getTransactionItem( }; } -type PartialSpanItem = Omit; - -function getSpanItem(span: Span): PartialSpanItem { +function getSpanItem(span: Span): IWaterfallItemSpan { if (span.version === 'v1') { return { id: span.span.id, @@ -107,53 +103,41 @@ function getSpanItem(span: Span): PartialSpanItem { }; } -export function getWaterfallRoot( - items: Array, +export function getWaterfallItems( + items: IWaterfallItem[], entryTransactionItem: IWaterfallItem ) { - const itemsByParentId = groupBy( + const itemsById: IWaterfallIndex = indexBy(items, 'id'); + const childrenByParentId = 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 - }; - } - // 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; + function getOrderedItems(item: IWaterfallItem): IWaterfallItem[] { + const children = sortBy(childrenByParentId[item.id] || [], 'timestamp'); - return fullItem; + // mutating to ensure both data structures ("itemsById" and "items") have the same data + item.childIds = children.map(child => child.id); + item.offset = item.timestamp - entryTransactionItem.timestamp; + + const deepChildren = flatten(children.map(getOrderedItems)); + return [item, ...deepChildren]; } - return { root: getWithChildren(entryTransactionItem), itemsById }; + return { + items: getOrderedItems(entryTransactionItem), + itemsById + }; +} + +function getRootTransaction(filteredHits: IWaterfallItem[]) { + const rootTransaction = filteredHits.find(hit => { + return hit.docType === 'transaction' && !hit.parentId; + }); + + if (rootTransaction && rootTransaction.docType === 'transaction') { + return rootTransaction.transaction; + } } export function getWaterfall( @@ -161,7 +145,7 @@ export function getWaterfall( services: string[], entryTransaction: Transaction ): IWaterfall { - const items = hits + const filteredHits = hits .filter(hit => { const docType = hit.processor.event; return ['span', 'transaction'].includes(docType); @@ -178,13 +162,32 @@ export function getWaterfall( } }); + const rootTransaction = getRootTransaction(filteredHits); const entryTransactionItem = getTransactionItem(entryTransaction); - const { root, itemsById } = getWaterfallRoot(items, entryTransactionItem); + const { items, itemsById } = getWaterfallItems( + filteredHits, + entryTransactionItem + ); + + const getTransactionById = (id?: IWaterfallItem['id']) => { + if (!id) { + return; + } + + const item = itemsById[id]; + if (item.docType === 'transaction') { + return item.transaction; + } + }; + return { - duration: root.duration, + rootTransaction, + rootTransactionDuration: + rootTransaction && rootTransaction.transaction.duration.us, + duration: entryTransaction.transaction.duration.us, services, - childrenCount: hits.length, - root, - itemsById + 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 ebdd98abd4d31..a48f944b8f2cd 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 76c8eada51a0d..37184494954f4 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 @@ -13,7 +13,7 @@ import { EuiTitle, EuiToolTip } from '@elastic/eui'; -import { isEmpty } from 'lodash'; +import { get, isEmpty } from 'lodash'; import React from 'react'; import { Transaction as ITransaction } from '../../../../../typings/Transaction'; import { IUrlParams } from '../../../../store/urlParams'; @@ -21,20 +21,21 @@ import EmptyMessage from '../../../shared/EmptyMessage'; import { TransactionLink } from '../../../shared/TransactionLink'; import { ActionMenu } 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 === get(waterfall.rootTransaction, 'id'); - if (isRoot || !root) { + let button; + if (isRoot) { button = ( @@ -48,7 +49,9 @@ function MaybeViewTraceLink({ return ( - {button} + + {button} + ); } @@ -57,14 +60,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 ( @@ -75,8 +78,6 @@ export const Transaction: React.SFC = ({ ); } - const root = waterfallRoot || transaction; - return ( @@ -91,14 +92,20 @@ export const Transaction: React.SFC = ({ - + - + @@ -106,6 +113,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 af24413928283..4140607a50384 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.ts +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.ts @@ -6,7 +6,6 @@ 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 @@ -22,8 +21,7 @@ interface Props { function mapStateToProps(state: any = {}, props: Partial) { return { location: state.location, - urlParams: getUrlParams(state), - waterfallRoot: selectWaterfallRoot(state, props) + urlParams: getUrlParams(state) }; } 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 6ddc0c85c2161..0ed4f80fd353f 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 a78131a4c352b..eab7735b5073e 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/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 887bb2a16dcbe..1bf8dc5e29c77 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 26f2d46add30c..b52644d6881d5 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 0000000000000..d291986e3bce3 --- /dev/null +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/waterfall.tsx @@ -0,0 +1,71 @@ +/* + * 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 { WaterfallResponse } from 'x-pack/plugins/apm/typings/waterfall'; +import { + getWaterfall, + 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 WaterfallV1OrV2Props { + urlParams: IUrlParams; + transaction: Transaction; + render: RRRRender; +} + +function WaterfallV1OrV2({ + urlParams, + transaction, + render +}: WaterfallV1OrV2Props) { + const hasTrace = transaction.hasOwnProperty('trace'); + if (hasTrace) { + return ( + + ); + } else { + return ( + + ); + } +} + +interface WaterfallRequestProps { + urlParams: IUrlParams; + transaction: Transaction; + render: RRRRender; +} + +export function WaterfallRequest({ + urlParams, + transaction, + render +}: WaterfallRequestProps) { + return ( + { + const waterfall = getWaterfall(data.hits, data.services, transaction); + return render({ status, args, data: waterfall }); + }} + /> + ); +} 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 632a3c001b387..0000000000000 --- 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/typings/react-redux-request.d.ts b/x-pack/plugins/apm/typings/react-redux-request.d.ts index abee82e2f6754..23a3b7578772c 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 {