[APM] Hoist loading of waterfall and flatten it (#24651)

* [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
This commit is contained in:
Søren Louv-Jansen 2018-10-31 10:27:57 +01:00 committed by GitHub
parent 5a019cc31c
commit 8f4b0e9bf8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 522 additions and 676 deletions

View file

@ -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';

View file

@ -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';

View file

@ -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) {
<EuiSpacer />
<TraceListRequest
urlParams={urlParams}
render={({ data, status }: RRRRenderArgs<ITransactionGroup[]>) => (
render={({ data, status }: RRRRenderResponse<ITransactionGroup[]>) => (
<TraceList
items={data}
isLoading={status === 'LOADING'}

View file

@ -6,8 +6,6 @@
import { get } from 'lodash';
import React from 'react';
import { connect } from 'react-redux';
import { selectWaterfallRoot } from 'x-pack/plugins/apm/public/store/selectors/waterfall';
import {
REQUEST_URL_FULL,
TRANSACTION_DURATION,
@ -17,7 +15,6 @@ import {
import { Transaction } from '../../../../../typings/Transaction';
// @ts-ignore
import { asTime } from '../../../../utils/formatters';
// @ts-ignore
import {
IStickyProperty,
StickyProperties
@ -25,27 +22,26 @@ import {
function getDurationPercent(
transactionDuration: number,
rootDuration?: number
totalDuration?: number
) {
if (rootDuration === undefined || rootDuration === 0) {
if (!totalDuration) {
return '';
}
return ((transactionDuration / rootDuration) * 100).toFixed(2) + '%';
return ((transactionDuration / totalDuration) * 100).toFixed(2) + '%';
}
interface Props {
transaction: Transaction;
root?: Transaction;
totalDuration?: number;
}
export function StickyTransactionPropertiesComponent({
export function StickyTransactionProperties({
transaction,
root
totalDuration
}: Props) {
const timestamp = get(transaction, '@timestamp');
const timestamp = transaction['@timestamp'];
const url = get(transaction, REQUEST_URL_FULL, 'N/A');
const duration = transaction.transaction.duration.us;
const rootDuration = root && root.transaction.duration.us;
const stickyProperties: IStickyProperty[] = [
{
label: 'Timestamp',
@ -69,7 +65,7 @@ export function StickyTransactionPropertiesComponent({
},
{
label: '% of trace',
val: getDurationPercent(duration, rootDuration),
val: getDurationPercent(duration, totalDuration),
width: '25%'
},
{
@ -89,11 +85,3 @@ export function StickyTransactionPropertiesComponent({
return <StickyProperties stickyProperties={stickyProperties} />;
}
const mapStateToProps = (state: any, props: Partial<Props>) => ({
root: selectWaterfallRoot(state, props)
});
export const StickyTransactionProperties = connect<{}, {}, Props>(
mapStateToProps
)(StickyTransactionPropertiesComponent);

View file

@ -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<
)}
</div>
);
};
}

View file

@ -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 (
<Legends>
<EuiTitle size="xxxs">
<span>Services</span>
</EuiTitle>
{Object.entries(serviceColors).map(([label, color]) => (
<Legend key={color} color={color} text={label} />
))}

View file

@ -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',

View file

@ -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;

View file

@ -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 (

View file

@ -66,7 +66,10 @@ export function TransactionFlyout({
<EuiFlyoutBody>
<FlyoutTopLevelProperties transaction={transactionDoc} />
<EuiHorizontalRule />
<StickyTransactionProperties transaction={transactionDoc} />
<StickyTransactionProperties
transaction={transactionDoc}
totalDuration={waterfall.traceRootDuration}
/>
<EuiHorizontalRule />
<TransactionPropertiesTableForFlyout
transaction={transactionDoc}

View file

@ -88,7 +88,7 @@ interface ITimelineMargins {
interface IWaterfallItemProps {
timelineMargins: ITimelineMargins;
totalDuration: number;
totalDuration?: number;
item: IWaterfallItem;
color: string;
isSelected: boolean;
@ -115,8 +115,12 @@ export function WaterfallItem({
isSelected,
onClick
}: IWaterfallItemProps) {
if (!totalDuration) {
return null;
}
const width = (item.duration / totalDuration) * 100;
const left = (item.offset / totalDuration) * 100;
const left = ((item.offset + item.skew) / totalDuration) * 100;
const Label = item.docType === 'transaction' ? TransactionLabel : SpanLabel;
// Note: the <Prefix> appears *after* the item name in the DOM order

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Component, Fragment } from 'react';
import React, { Component } from 'react';
// @ts-ignore
import { StickyContainer } from 'react-sticky';
import styled from 'styled-components';
@ -62,26 +62,19 @@ export class Waterfall extends Component<Props> {
});
};
public renderWaterfall = (item?: IWaterfallItem) => {
if (!item) {
return null;
}
public getWaterfallItem = (item: IWaterfallItem) => {
const { serviceColors, waterfall, urlParams }: Props = this.props;
return (
<Fragment key={item.id}>
<WaterfallItem
timelineMargins={TIMELINE_MARGINS}
color={serviceColors[item.serviceName]}
item={item}
totalDuration={waterfall.duration}
isSelected={item.id === urlParams.waterfallItemId}
onClick={() => this.onOpenFlyout(item)}
/>
{item.children && item.children.map(this.renderWaterfall)}
</Fragment>
<WaterfallItem
key={item.id}
timelineMargins={TIMELINE_MARGINS}
color={serviceColors[item.serviceName]}
item={item}
totalDuration={waterfall.duration}
isSelected={item.id === urlParams.waterfallItemId}
onClick={() => this.onOpenFlyout(item)}
/>
);
};
@ -98,11 +91,15 @@ export class Waterfall extends Component<Props> {
switch (currentItem.docType) {
case 'span':
const parentTransaction = waterfall.getTransactionById(
currentItem.parentId
);
return (
<SpanFlyout
totalDuration={waterfall.duration}
span={currentItem.span}
parentTransaction={currentItem.parentTransaction}
parentTransaction={parentTransaction}
onClose={this.onCloseFlyout}
/>
);
@ -124,7 +121,7 @@ export class Waterfall extends Component<Props> {
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 (
<Container>
@ -140,7 +137,7 @@ export class Waterfall extends Component<Props> {
paddingTop: TIMELINE_MARGINS.top
}}
>
{this.renderWaterfall(waterfall.root)}
{waterfall.items.map(this.getWaterfallItem)}
</div>
</StickyContainer>

View file

@ -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,
},
]
`;

View file

@ -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();
});
});
});

View file

@ -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<IWaterfallItemBase['id']>;
}
interface IWaterfallItemTransaction extends IWaterfallItemBase {
transaction: Transaction;
docType: 'transaction';
children?: Array<IWaterfallItemSpan | IWaterfallItemTransaction>;
}
interface IWaterfallItemSpan extends IWaterfallItemBase {
parentTransaction: Transaction;
span: Span;
docType: 'span';
children?: Array<IWaterfallItemSpan | IWaterfallItemTransaction>;
}
export type IWaterfallItem = IWaterfallItemSpan | IWaterfallItemTransaction;
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
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<IWaterfallItemSpan, 'parentTransaction'>;
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<PartialSpanItem | IWaterfallItemTransaction>,
function getClockSkew(
item: IWaterfallItem,
itemsById: IWaterfallIndex,
parentTransactionSkew: number
) {
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;
}
}
}
export function getWaterfallItems(
childrenByParentId: IWaterfallGroup,
itemsById: IWaterfallIndex,
entryTransactionItem: IWaterfallItem
) {
const itemsByParentId = groupBy(
items,
item => (item.parentId ? item.parentId : 'root')
);
const itemsById: IWaterfallIndex = {};
function getSortedChildren(
item: IWaterfallItem,
parentTransactionSkew: number
): IWaterfallItem[] {
const skew = getClockSkew(item, itemsById, parentTransactionSkew);
const children = sortBy(childrenByParentId[item.id] || [], 'timestamp');
const itemsByTransactionId = indexBy(
items.filter(item => item.docType === 'transaction'),
item => item.id
) as { [key: string]: IWaterfallItemTransaction };
item.childIds = children.map(child => child.id);
item.offset = item.timestamp - entryTransactionItem.timestamp;
item.skew = skew;
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;
return fullItem;
const deepChildren = flatten(
children.map(child => getSortedChildren(child, skew))
);
return [item, ...deepChildren];
}
return { root: getWithChildren(entryTransactionItem), itemsById };
return getSortedChildren(entryTransactionItem, 0);
}
function getTraceRoot(childrenByParentId: IWaterfallGroup) {
const item = first(childrenByParentId.root);
if (item && item.docType === 'transaction') {
return item.transaction;
}
}
function getServices(items: IWaterfallItem[]) {
const serviceNames = items.map(item => item.serviceName);
return uniq(serviceNames);
}
export function getWaterfall(
hits: Array<Span | Transaction>,
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
};
}

View file

@ -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<WaterfallResponse>;
}
function WaterfallRequest({
urlParams,
transaction,
render
}: WaterfallRequestProps) {
const hasTrace = transaction.hasOwnProperty('trace');
if (hasTrace) {
return (
<WaterfallV2Request
urlParams={urlParams}
transaction={transaction}
render={render}
/>
);
} else {
return (
<WaterfallV1Request
urlParams={urlParams}
transaction={transaction}
render={render}
/>
);
}
waterfall: IWaterfall;
}
export function WaterfallContainer({
location,
urlParams,
transaction
transaction,
waterfall
}: Props) {
return (
<WaterfallRequest
urlParams={urlParams}
transaction={transaction}
render={({ data }) => {
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 (
<div>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem>
<ServiceLegends serviceColors={serviceColors} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiToolTip
content="Distributed tracing is now supported as a beta feature of the APM UI timeline visualisation."
position="left"
>
<EuiBadge color="hollow">Beta</EuiBadge>
</EuiToolTip>
</EuiFlexItem>
</EuiFlexGroup>
<Waterfall
agentMarks={agentMarks}
location={location}
serviceColors={serviceColors}
urlParams={urlParams}
waterfall={waterfall}
/>
</div>
);
}}
/>
return (
<div>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem>
<ServiceLegends serviceColors={serviceColors} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiToolTip
content="Distributed tracing is now supported as a beta feature of the APM UI timeline visualisation."
position="left"
>
<EuiBadge color="hollow">Beta</EuiBadge>
</EuiToolTip>
</EuiFlexItem>
</EuiFlexGroup>
<Waterfall
agentMarks={agentMarks}
location={location}
serviceColors={serviceColors}
urlParams={urlParams}
waterfall={waterfall}
/>
</div>
);
}

View file

@ -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 = (
<EuiToolTip content="Currently viewing the full trace">
<EuiButton iconType="apmApp" disabled={true}>
@ -49,7 +51,9 @@ function MaybeViewTraceLink({
return (
<EuiFlexItem grow={false}>
<TransactionLink transaction={root}>{button}</TransactionLink>
<TransactionLink transaction={waterfall.traceRoot}>
{button}
</TransactionLink>
</EuiFlexItem>
);
}
@ -58,14 +62,14 @@ interface Props {
transaction: ITransaction;
urlParams: IUrlParams;
location: Location;
waterfallRoot?: ITransaction;
waterfall: IWaterfall;
}
export const Transaction: React.SFC<Props> = ({
transaction,
urlParams,
location,
waterfallRoot
waterfall
}) => {
if (isEmpty(transaction)) {
return (
@ -76,8 +80,6 @@ export const Transaction: React.SFC<Props> = ({
);
}
const root = waterfallRoot || transaction;
return (
<EuiPanel paddingSize="m">
<EuiFlexGroup justifyContent="spaceBetween">
@ -96,14 +98,20 @@ export const Transaction: React.SFC<Props> = ({
</EuiButtonEmpty>
</DiscoverTransactionLink>
</EuiFlexItem>
<MaybeViewTraceLink transaction={transaction} root={root} />
<MaybeViewTraceLink
transaction={transaction}
waterfall={waterfall}
/>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer />
<StickyTransactionProperties transaction={transaction} root={root} />
<StickyTransactionProperties
transaction={transaction}
totalDuration={waterfall.traceRootDuration}
/>
<EuiSpacer />
@ -111,6 +119,7 @@ export const Transaction: React.SFC<Props> = ({
transaction={transaction}
location={location}
urlParams={urlParams}
waterfall={waterfall}
/>
</EuiPanel>
);

View file

@ -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<Props>) {
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
);

View file

@ -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 (
<div>
<HeaderLarge>{urlParams.transactionName}</HeaderLarge>
@ -44,7 +39,7 @@ export function TransactionDetailsView({
<TransactionDetailsChartsRequest
urlParams={urlParams}
render={({ data }: RRRRenderArgs<any>) => (
render={({ data }: RRRRenderResponse<any>) => (
<TransactionCharts
charts={data}
urlParams={urlParams}
@ -55,7 +50,7 @@ export function TransactionDetailsView({
<TransactionDistributionRequest
urlParams={urlParams}
render={({ data }: RRRRenderArgs<any>) => (
render={({ data }) => (
<Distribution
distribution={data}
urlParams={urlParams}
@ -68,13 +63,21 @@ export function TransactionDetailsView({
<TransactionDetailsRequest
urlParams={urlParams}
render={(res: RRRRenderArgs<any>) => {
render={({ data: transaction }) => {
return (
<Transaction
location={location}
transaction={res.data}
<WaterfallRequest
urlParams={urlParams}
waterfallRoot={waterfallRoot}
transaction={transaction}
render={({ data: waterfall }) => {
return (
<Transaction
location={location}
transaction={transaction}
urlParams={urlParams}
waterfall={waterfall}
/>
);
}}
/>
);
}}

View file

@ -9,7 +9,7 @@ import { Transaction } from '../../../typings/Transaction';
import { KibanaLink, legacyEncodeURIComponent } from '../../utils/url';
interface TransactionLinkProps {
transaction: Transaction;
transaction?: Transaction;
}
/**

View file

@ -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,

View file

@ -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({

View file

@ -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<Transaction>;
}) {
const { serviceName, start, end, transactionId, traceId, kuery } = urlParams;
if (!(serviceName && start && end && transactionId)) {

View file

@ -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<T> {
data: T;
}
export function getTransactionDistribution(
state: IReduxState
): RrrResponse<IDistributionResponse> {
): RRRRenderResponse<IDistributionResponse> {
return withInitialData(state.reactReduxRequest[ID]);
}
@ -41,7 +37,7 @@ export function TransactionDistributionRequest({
render
}: {
urlParams: IUrlParams;
render: (args: RRRRenderArgs<any>) => any;
render: RRRRender<IDistributionResponse>;
}) {
const { serviceName, start, end, transactionName, kuery } = urlParams;

View file

@ -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<IWaterfall>;
}
export function WaterfallRequest({ urlParams, transaction, render }: Props) {
const hasTrace = transaction.hasOwnProperty('trace');
if (hasTrace) {
return (
<WaterfallV2Request
urlParams={urlParams}
transaction={transaction}
render={render}
/>
);
} else {
return (
<WaterfallV1Request
urlParams={urlParams}
transaction={transaction}
render={render}
/>
);
}
}

View file

@ -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<WaterfallResponse>;
render: RRRRender<IWaterfall>;
}
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 });
}}
/>
);

View file

@ -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<WaterfallResponse>;
render: RRRRender<IWaterfall>;
}
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 });
}}
/>
);
}

View file

@ -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<WaterfallResponse>;
[v2ID]?: RRRRenderArgs<WaterfallResponse>;
};
}
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;
}
);

View file

@ -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);
}

View file

@ -9,14 +9,14 @@
declare module 'react-redux-request' {
import React from 'react';
export interface RRRRenderArgs<T, P = any[]> {
export interface RRRRenderResponse<T, P = any[]> {
status: 'SUCCESS' | 'LOADING' | 'FAILURE';
data: T;
args: P;
}
export type RRRRender<T, P = any[]> = (
args: RRRRenderArgs<T, P>
res: RRRRenderResponse<T, P>
) => JSX.Element | null;
export interface RequestProps<T, P> {

View file

@ -7,7 +7,4 @@
import { Span } from './Span';
import { Transaction } from './Transaction';
export interface WaterfallResponse {
services: string[];
hits: Array<Transaction | Span>;
}
export type WaterfallResponse = Array<Transaction | Span>;