mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[APM] Performance marks for RUM agent (#20931)
* [APM] Performance marks for agent * Fixed formatting and design * Update snapshot * Fixed tooltip id
This commit is contained in:
parent
bf6cc70d24
commit
7266349e33
16 changed files with 454 additions and 65 deletions
|
@ -30,3 +30,5 @@ export const ERROR_CULPRIT = 'error.culprit';
|
|||
export const ERROR_LOG_MESSAGE = 'error.log.message';
|
||||
export const ERROR_EXC_MESSAGE = 'error.exception.message';
|
||||
export const ERROR_EXC_HANDLED = 'error.exception.handled';
|
||||
|
||||
export const USER_ID = 'context.user.id';
|
||||
|
|
|
@ -29,7 +29,8 @@ import {
|
|||
SERVICE_NAME,
|
||||
ERROR_GROUP_ID,
|
||||
SERVICE_AGENT_NAME,
|
||||
SERVICE_LANGUAGE_NAME
|
||||
SERVICE_LANGUAGE_NAME,
|
||||
USER_ID
|
||||
} from '../../../../../common/constants';
|
||||
import { fromQuery, toQuery, history } from '../../../../utils/url';
|
||||
|
||||
|
@ -101,8 +102,8 @@ function DetailView({ errorGroup, urlParams, location }) {
|
|||
},
|
||||
{
|
||||
label: 'User ID',
|
||||
fieldName: 'context.user.id',
|
||||
val: get(errorGroup.data, 'error.context.user.id', 'N/A')
|
||||
fieldName: USER_ID,
|
||||
val: get(errorGroup.data.error, USER_ID, 'N/A')
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ export default function TimelineHeader({ legends, transactionName }) {
|
|||
</TooltipOverlay>
|
||||
<Legends>
|
||||
{legends.map(({ color, label }) => (
|
||||
<Legend clickable={false} key={color} color={color} text={label} />
|
||||
<Legend key={color} color={color} text={label} />
|
||||
))}
|
||||
</Legends>
|
||||
</TimelineHeaderContainer>
|
||||
|
|
|
@ -43,7 +43,13 @@ const TIMELINE_MARGINS = {
|
|||
|
||||
class Spans extends PureComponent {
|
||||
render() {
|
||||
const { agentName, urlParams, location, droppedSpans } = this.props;
|
||||
const {
|
||||
agentName,
|
||||
urlParams,
|
||||
location,
|
||||
droppedSpans,
|
||||
agentMarks
|
||||
} = this.props;
|
||||
return (
|
||||
<SpansRequest
|
||||
urlParams={urlParams}
|
||||
|
@ -81,6 +87,7 @@ class Spans extends PureComponent {
|
|||
transactionName={urlParams.transactionName}
|
||||
/>
|
||||
}
|
||||
agentMarks={agentMarks}
|
||||
duration={totalDuration}
|
||||
height={timelineHeight}
|
||||
margins={TIMELINE_MARGINS}
|
||||
|
@ -176,6 +183,7 @@ function getPrimaryType(type) {
|
|||
}
|
||||
|
||||
Spans.propTypes = {
|
||||
agentMarks: PropTypes.array,
|
||||
agentName: PropTypes.string.isRequired,
|
||||
droppedSpans: PropTypes.number.isRequired,
|
||||
location: PropTypes.object.isRequired,
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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 { getAgentMarks } from '../view';
|
||||
|
||||
describe('TransactionDetailsView', () => {
|
||||
describe('getAgentMarks', () => {
|
||||
it('should be sorted', () => {
|
||||
const transaction = {
|
||||
transaction: {
|
||||
marks: {
|
||||
agent: {
|
||||
domInteractive: 117,
|
||||
timeToFirstByte: 10,
|
||||
domComplete: 118
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
expect(getAgentMarks(transaction)).toEqual([
|
||||
{ name: 'timeToFirstByte', timeLabel: 10000, timeAxis: 10000 },
|
||||
{ name: 'domInteractive', timeLabel: 117000, timeAxis: 117000 },
|
||||
{ name: 'domComplete', timeLabel: 118000, timeAxis: 118000 }
|
||||
]);
|
||||
});
|
||||
|
||||
it('should ensure they are not too close', () => {
|
||||
const transaction = {
|
||||
transaction: {
|
||||
duration: {
|
||||
us: 1000 * 1000
|
||||
},
|
||||
marks: {
|
||||
agent: {
|
||||
a: 0,
|
||||
b: 10,
|
||||
c: 11,
|
||||
d: 12,
|
||||
e: 968,
|
||||
f: 969,
|
||||
timeToFirstByte: 970,
|
||||
domInteractive: 980,
|
||||
domComplete: 990
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
expect(getAgentMarks(transaction)).toEqual([
|
||||
{ timeLabel: 0, name: 'a', timeAxis: 0 },
|
||||
{ timeLabel: 10000, name: 'b', timeAxis: 20000 },
|
||||
{ timeLabel: 11000, name: 'c', timeAxis: 40000 },
|
||||
{ timeLabel: 12000, name: 'd', timeAxis: 60000 },
|
||||
{ timeLabel: 968000, name: 'e', timeAxis: 910000 },
|
||||
{ timeLabel: 969000, name: 'f', timeAxis: 930000 },
|
||||
{ timeLabel: 970000, name: 'timeToFirstByte', timeAxis: 950000 },
|
||||
{ timeLabel: 980000, name: 'domInteractive', timeAxis: 970000 },
|
||||
{ timeLabel: 990000, name: 'domComplete', timeAxis: 990000 }
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -15,7 +15,7 @@ import {
|
|||
borderRadius
|
||||
} from '../../../../style/variables';
|
||||
import { Tab, HeaderMedium } from '../../../shared/UIComponents';
|
||||
import { isEmpty, capitalize, get } from 'lodash';
|
||||
import { isEmpty, capitalize, get, sortBy, last } from 'lodash';
|
||||
|
||||
import { ContextProperties } from '../../../shared/ContextProperties';
|
||||
import {
|
||||
|
@ -27,7 +27,10 @@ import DiscoverButton from '../../../shared/DiscoverButton';
|
|||
import {
|
||||
TRANSACTION_ID,
|
||||
PROCESSOR_EVENT,
|
||||
SERVICE_AGENT_NAME
|
||||
SERVICE_AGENT_NAME,
|
||||
TRANSACTION_DURATION,
|
||||
TRANSACTION_RESULT,
|
||||
USER_ID
|
||||
} from '../../../../../common/constants';
|
||||
import { fromQuery, toQuery, history } from '../../../../utils/url';
|
||||
import { asTime } from '../../../../utils/formatters';
|
||||
|
@ -62,6 +65,48 @@ const PropertiesTableContainer = styled.div`
|
|||
|
||||
const DEFAULT_TAB = 'timeline';
|
||||
|
||||
export function getAgentMarks(transaction) {
|
||||
const duration = get(transaction, TRANSACTION_DURATION);
|
||||
const threshold = duration / 100 * 2;
|
||||
|
||||
return sortBy(
|
||||
Object.entries(get(transaction, 'transaction.marks.agent', [])),
|
||||
'1'
|
||||
)
|
||||
.map(([name, ms]) => ({
|
||||
name,
|
||||
timeLabel: ms * 1000,
|
||||
timeAxis: ms * 1000
|
||||
}))
|
||||
.reduce((acc, curItem) => {
|
||||
const prevTime = get(last(acc), 'timeAxis');
|
||||
const nextValidTime = prevTime + threshold;
|
||||
const isTooClose = prevTime != null && nextValidTime > curItem.timeAxis;
|
||||
const canFit = nextValidTime <= duration;
|
||||
|
||||
if (isTooClose && canFit) {
|
||||
acc.push({ ...curItem, timeAxis: nextValidTime });
|
||||
} else {
|
||||
acc.push(curItem);
|
||||
}
|
||||
return acc;
|
||||
}, [])
|
||||
.reduceRight((acc, curItem) => {
|
||||
const prevTime = get(last(acc), 'timeAxis');
|
||||
const nextValidTime = prevTime - threshold;
|
||||
const isTooClose = prevTime != null && nextValidTime < curItem.timeAxis;
|
||||
const canFit = nextValidTime >= 0;
|
||||
|
||||
if (isTooClose && canFit) {
|
||||
acc.push({ ...curItem, timeAxis: nextValidTime });
|
||||
} else {
|
||||
acc.push(curItem);
|
||||
}
|
||||
return acc;
|
||||
}, [])
|
||||
.reverse();
|
||||
}
|
||||
|
||||
// Ensure the selected tab exists or use the default
|
||||
function getCurrentTab(tabs = [], detailTab) {
|
||||
return tabs.includes(detailTab) ? detailTab : DEFAULT_TAB;
|
||||
|
@ -86,22 +131,22 @@ function Transaction({ transaction, location, urlParams }) {
|
|||
|
||||
const timestamp = get(transaction, '@timestamp');
|
||||
const url = get(transaction, 'context.request.url.full', 'N/A');
|
||||
const duration = get(transaction, 'transaction.duration.us');
|
||||
const duration = get(transaction, TRANSACTION_DURATION);
|
||||
const stickyProperties = [
|
||||
{
|
||||
label: 'Duration',
|
||||
fieldName: 'transaction.duration.us',
|
||||
fieldName: TRANSACTION_DURATION,
|
||||
val: duration ? asTime(duration) : 'N/A'
|
||||
},
|
||||
{
|
||||
label: 'Result',
|
||||
fieldName: 'transaction.result',
|
||||
val: get(transaction, 'transaction.result', 'N/A')
|
||||
fieldName: TRANSACTION_RESULT,
|
||||
val: get(transaction, TRANSACTION_RESULT, 'N/A')
|
||||
},
|
||||
{
|
||||
label: 'User ID',
|
||||
fieldName: 'context.user.id',
|
||||
val: get(transaction, 'context.user.id', 'N/A')
|
||||
fieldName: USER_ID,
|
||||
val: get(transaction, USER_ID, 'N/A')
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -168,6 +213,7 @@ function Transaction({ transaction, location, urlParams }) {
|
|||
{currentTab === DEFAULT_TAB ? (
|
||||
<Spans
|
||||
agentName={agentName}
|
||||
agentMarks={getAgentMarks(transaction)}
|
||||
droppedSpans={get(
|
||||
transaction,
|
||||
'transaction.spanCount.dropped.total',
|
||||
|
|
|
@ -1271,7 +1271,7 @@ Array [
|
|||
align-items: center;
|
||||
font-size: 12px;
|
||||
color: #666666;
|
||||
cursor: pointer;
|
||||
cursor: initial;
|
||||
opacity: 1;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
|
|
|
@ -30,21 +30,21 @@ export default class Legend extends PureComponent {
|
|||
render() {
|
||||
const {
|
||||
onClick,
|
||||
color,
|
||||
text,
|
||||
color = colors.apmBlue,
|
||||
fontSize = fontSizes.small,
|
||||
radius = units.minus - 1,
|
||||
disabled = false,
|
||||
clickable = true,
|
||||
className
|
||||
clickable = false,
|
||||
...rest
|
||||
} = this.props;
|
||||
return (
|
||||
<Container
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
clickable={clickable}
|
||||
clickable={clickable || Boolean(onClick)}
|
||||
fontSize={fontSize}
|
||||
className={className}
|
||||
{...rest}
|
||||
>
|
||||
<Indicator color={color} radius={radius} />
|
||||
{text}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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 PropTypes from 'prop-types';
|
||||
import { EuiToolTip } from '@elastic/eui';
|
||||
import Legend from '../Legend';
|
||||
import { colors, units, px } from '../../../../style/variables';
|
||||
import styled from 'styled-components';
|
||||
import { asTime } from '../../../../utils/formatters';
|
||||
|
||||
const NameContainer = styled.div`
|
||||
border-bottom: 1px solid ${colors.gray3};
|
||||
padding-bottom: ${px(units.half)};
|
||||
`;
|
||||
|
||||
const TimeContainer = styled.div`
|
||||
color: ${colors.gray3};
|
||||
padding-top: ${px(units.half)};
|
||||
`;
|
||||
|
||||
export default function AgentMarker({ agentMark, x }) {
|
||||
const legendWidth = 11;
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: px(x - legendWidth / 2)
|
||||
}}
|
||||
>
|
||||
<EuiToolTip
|
||||
id={agentMark.name}
|
||||
position="top"
|
||||
content={
|
||||
<div>
|
||||
<NameContainer>{agentMark.name}</NameContainer>
|
||||
<TimeContainer>{asTime(agentMark.timeLabel)}</TimeContainer>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Legend clickable color={colors.gray3} />
|
||||
</EuiToolTip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
AgentMarker.propTypes = {
|
||||
agentMark: PropTypes.object.isRequired,
|
||||
x: PropTypes.number.isRequired
|
||||
};
|
|
@ -5,10 +5,12 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import _ from 'lodash';
|
||||
import { Sticky } from 'react-sticky';
|
||||
import { XYPlot, XAxis } from 'react-vis';
|
||||
import LastTickValue from './LastTickValue';
|
||||
import AgentMarker from './AgentMarker';
|
||||
import { colors, px } from '../../../../style/variables';
|
||||
import { getTimeFormatter } from '../../../../utils/formatters';
|
||||
|
||||
|
@ -16,7 +18,7 @@ import { getTimeFormatter } from '../../../../utils/formatters';
|
|||
const getXAxisTickValues = (tickValues, xMax) =>
|
||||
_.last(tickValues) * 1.05 > xMax ? tickValues.slice(0, -1) : tickValues;
|
||||
|
||||
function TimelineAxis({ header, plotValues }) {
|
||||
function TimelineAxis({ header, plotValues, agentMarks }) {
|
||||
const { margins, tickValues, width, xDomain, xMax, xScale } = plotValues;
|
||||
const tickFormat = getTimeFormatter(xMax);
|
||||
const xAxisTickValues = getXAxisTickValues(tickValues, xMax);
|
||||
|
@ -60,6 +62,14 @@ function TimelineAxis({ header, plotValues }) {
|
|||
/>
|
||||
|
||||
<LastTickValue x={xScale(xMax)} value={tickFormat(xMax)} />
|
||||
|
||||
{agentMarks.map(agentMark => (
|
||||
<AgentMarker
|
||||
key={agentMark.timeAxis}
|
||||
agentMark={agentMark}
|
||||
x={xScale(agentMark.timeAxis)}
|
||||
/>
|
||||
))}
|
||||
</XYPlot>
|
||||
</div>
|
||||
);
|
||||
|
@ -68,4 +78,14 @@ function TimelineAxis({ header, plotValues }) {
|
|||
);
|
||||
}
|
||||
|
||||
TimelineAxis.propTypes = {
|
||||
header: PropTypes.node,
|
||||
plotValues: PropTypes.object.isRequired,
|
||||
agentMarks: PropTypes.array
|
||||
};
|
||||
|
||||
TimelineAxis.defaultProps = {
|
||||
agentMarks: []
|
||||
};
|
||||
|
||||
export default TimelineAxis;
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
*/
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { XYPlot, VerticalGridLines } from 'react-vis';
|
||||
import { colors } from '../../../../style/variables';
|
||||
|
||||
export default class VerticalLines extends PureComponent {
|
||||
class VerticalLines extends PureComponent {
|
||||
render() {
|
||||
const {
|
||||
width,
|
||||
|
@ -19,6 +20,10 @@ export default class VerticalLines extends PureComponent {
|
|||
xMax
|
||||
} = this.props.plotValues;
|
||||
|
||||
const agentMarkTimes = this.props.agentMarks.map(
|
||||
({ timeAxis }) => timeAxis
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
|
@ -38,8 +43,9 @@ export default class VerticalLines extends PureComponent {
|
|||
tickValues={tickValues}
|
||||
style={{ stroke: colors.gray5 }}
|
||||
/>
|
||||
|
||||
<VerticalGridLines
|
||||
tickValues={[xMax]}
|
||||
tickValues={[...agentMarkTimes, xMax]}
|
||||
style={{ stroke: colors.gray3 }}
|
||||
/>
|
||||
</XYPlot>
|
||||
|
@ -47,3 +53,14 @@ export default class VerticalLines extends PureComponent {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLines.propTypes = {
|
||||
plotValues: PropTypes.object.isRequired,
|
||||
agentMarks: PropTypes.array
|
||||
};
|
||||
|
||||
VerticalLines.defaultProps = {
|
||||
agentMarks: []
|
||||
};
|
||||
|
||||
export default VerticalLines;
|
||||
|
|
|
@ -1,6 +1,33 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Timeline should render with data 1`] = `
|
||||
.c0 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
color: #666666;
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
margin-right: 5.5px;
|
||||
background: #999999;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
<div
|
||||
onScroll={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
|
@ -38,7 +65,7 @@ exports[`Timeline should render with data 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"height": "40px",
|
||||
"width": "100px",
|
||||
"width": "1000px",
|
||||
}
|
||||
}
|
||||
>
|
||||
|
@ -52,7 +79,7 @@ exports[`Timeline should render with data 1`] = `
|
|||
onMouseLeave={[Function]}
|
||||
onMouseMove={[Function]}
|
||||
onWheel={[Function]}
|
||||
width={100}
|
||||
width={1000}
|
||||
>
|
||||
<g
|
||||
className="rv-xy-plot__axis rv-xy-plot__axis--horizontal "
|
||||
|
@ -120,7 +147,7 @@ exports[`Timeline should render with data 1`] = `
|
|||
},
|
||||
}
|
||||
}
|
||||
transform="translate(0, 0)"
|
||||
transform="translate(87.9408646541237, 0)"
|
||||
>
|
||||
<line
|
||||
className="rv-xy-plot__axis__tick__line"
|
||||
|
@ -162,7 +189,7 @@ exports[`Timeline should render with data 1`] = `
|
|||
},
|
||||
}
|
||||
}
|
||||
transform="translate(0, 0)"
|
||||
transform="translate(175.8817293082474, 0)"
|
||||
>
|
||||
<line
|
||||
className="rv-xy-plot__axis__tick__line"
|
||||
|
@ -204,7 +231,7 @@ exports[`Timeline should render with data 1`] = `
|
|||
},
|
||||
}
|
||||
}
|
||||
transform="translate(0, 0)"
|
||||
transform="translate(263.82259396237106, 0)"
|
||||
>
|
||||
<line
|
||||
className="rv-xy-plot__axis__tick__line"
|
||||
|
@ -246,7 +273,7 @@ exports[`Timeline should render with data 1`] = `
|
|||
},
|
||||
}
|
||||
}
|
||||
transform="translate(0, 0)"
|
||||
transform="translate(351.7634586164948, 0)"
|
||||
>
|
||||
<line
|
||||
className="rv-xy-plot__axis__tick__line"
|
||||
|
@ -288,7 +315,7 @@ exports[`Timeline should render with data 1`] = `
|
|||
},
|
||||
}
|
||||
}
|
||||
transform="translate(0, 0)"
|
||||
transform="translate(439.7043232706185, 0)"
|
||||
>
|
||||
<line
|
||||
className="rv-xy-plot__axis__tick__line"
|
||||
|
@ -330,7 +357,7 @@ exports[`Timeline should render with data 1`] = `
|
|||
},
|
||||
}
|
||||
}
|
||||
transform="translate(0, 0)"
|
||||
transform="translate(527.6451879247421, 0)"
|
||||
>
|
||||
<line
|
||||
className="rv-xy-plot__axis__tick__line"
|
||||
|
@ -372,7 +399,7 @@ exports[`Timeline should render with data 1`] = `
|
|||
},
|
||||
}
|
||||
}
|
||||
transform="translate(0, 0)"
|
||||
transform="translate(615.5860525788659, 0)"
|
||||
>
|
||||
<line
|
||||
className="rv-xy-plot__axis__tick__line"
|
||||
|
@ -414,7 +441,7 @@ exports[`Timeline should render with data 1`] = `
|
|||
},
|
||||
}
|
||||
}
|
||||
transform="translate(0, 0)"
|
||||
transform="translate(703.5269172329896, 0)"
|
||||
>
|
||||
<line
|
||||
className="rv-xy-plot__axis__tick__line"
|
||||
|
@ -456,7 +483,7 @@ exports[`Timeline should render with data 1`] = `
|
|||
},
|
||||
}
|
||||
}
|
||||
transform="translate(0, 0)"
|
||||
transform="translate(791.4677818871132, 0)"
|
||||
>
|
||||
<line
|
||||
className="rv-xy-plot__axis__tick__line"
|
||||
|
@ -492,7 +519,7 @@ exports[`Timeline should render with data 1`] = `
|
|||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="translate(50, 40)"
|
||||
transform="translate(950, 40)"
|
||||
>
|
||||
<text
|
||||
dy="0"
|
||||
|
@ -503,6 +530,93 @@ exports[`Timeline should render with data 1`] = `
|
|||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"left": "484.2043232706185px",
|
||||
"position": "absolute",
|
||||
}
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="euiToolTipAnchor"
|
||||
>
|
||||
<div
|
||||
aria-describedby="timeToFirstByte"
|
||||
className="c0"
|
||||
disabled={false}
|
||||
fontSize="12px"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<span
|
||||
className="c1"
|
||||
color="#999999"
|
||||
radius={11}
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"left": "528.1747555976804px",
|
||||
"position": "absolute",
|
||||
}
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="euiToolTipAnchor"
|
||||
>
|
||||
<div
|
||||
aria-describedby="domInteractive"
|
||||
className="c0"
|
||||
disabled={false}
|
||||
fontSize="12px"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<span
|
||||
className="c1"
|
||||
color="#999999"
|
||||
radius={11}
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"left": "879.9382142141751px",
|
||||
"position": "absolute",
|
||||
}
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="euiToolTipAnchor"
|
||||
>
|
||||
<div
|
||||
aria-describedby="domComplete"
|
||||
className="c0"
|
||||
disabled={false}
|
||||
fontSize="12px"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<span
|
||||
className="c1"
|
||||
color="#999999"
|
||||
radius={11}
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -520,7 +634,7 @@ exports[`Timeline should render with data 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"height": "216px",
|
||||
"width": "100px",
|
||||
"width": "1000px",
|
||||
}
|
||||
}
|
||||
>
|
||||
|
@ -534,7 +648,7 @@ exports[`Timeline should render with data 1`] = `
|
|||
onMouseLeave={[Function]}
|
||||
onMouseMove={[Function]}
|
||||
onWheel={[Function]}
|
||||
width={100}
|
||||
width={1000}
|
||||
>
|
||||
<g
|
||||
className="rv-xy-plot__grid-lines"
|
||||
|
@ -559,8 +673,8 @@ exports[`Timeline should render with data 1`] = `
|
|||
"stroke": "#f5f5f5",
|
||||
}
|
||||
}
|
||||
x1={0}
|
||||
x2={0}
|
||||
x1={87.9408646541237}
|
||||
x2={87.9408646541237}
|
||||
y1={0}
|
||||
y2={116}
|
||||
/>
|
||||
|
@ -571,8 +685,8 @@ exports[`Timeline should render with data 1`] = `
|
|||
"stroke": "#f5f5f5",
|
||||
}
|
||||
}
|
||||
x1={0}
|
||||
x2={0}
|
||||
x1={175.8817293082474}
|
||||
x2={175.8817293082474}
|
||||
y1={0}
|
||||
y2={116}
|
||||
/>
|
||||
|
@ -583,8 +697,8 @@ exports[`Timeline should render with data 1`] = `
|
|||
"stroke": "#f5f5f5",
|
||||
}
|
||||
}
|
||||
x1={0}
|
||||
x2={0}
|
||||
x1={263.82259396237106}
|
||||
x2={263.82259396237106}
|
||||
y1={0}
|
||||
y2={116}
|
||||
/>
|
||||
|
@ -595,8 +709,8 @@ exports[`Timeline should render with data 1`] = `
|
|||
"stroke": "#f5f5f5",
|
||||
}
|
||||
}
|
||||
x1={0}
|
||||
x2={0}
|
||||
x1={351.7634586164948}
|
||||
x2={351.7634586164948}
|
||||
y1={0}
|
||||
y2={116}
|
||||
/>
|
||||
|
@ -607,8 +721,8 @@ exports[`Timeline should render with data 1`] = `
|
|||
"stroke": "#f5f5f5",
|
||||
}
|
||||
}
|
||||
x1={0}
|
||||
x2={0}
|
||||
x1={439.7043232706185}
|
||||
x2={439.7043232706185}
|
||||
y1={0}
|
||||
y2={116}
|
||||
/>
|
||||
|
@ -619,8 +733,8 @@ exports[`Timeline should render with data 1`] = `
|
|||
"stroke": "#f5f5f5",
|
||||
}
|
||||
}
|
||||
x1={0}
|
||||
x2={0}
|
||||
x1={527.6451879247421}
|
||||
x2={527.6451879247421}
|
||||
y1={0}
|
||||
y2={116}
|
||||
/>
|
||||
|
@ -631,8 +745,8 @@ exports[`Timeline should render with data 1`] = `
|
|||
"stroke": "#f5f5f5",
|
||||
}
|
||||
}
|
||||
x1={0}
|
||||
x2={0}
|
||||
x1={615.5860525788659}
|
||||
x2={615.5860525788659}
|
||||
y1={0}
|
||||
y2={116}
|
||||
/>
|
||||
|
@ -643,8 +757,8 @@ exports[`Timeline should render with data 1`] = `
|
|||
"stroke": "#f5f5f5",
|
||||
}
|
||||
}
|
||||
x1={0}
|
||||
x2={0}
|
||||
x1={703.5269172329896}
|
||||
x2={703.5269172329896}
|
||||
y1={0}
|
||||
y2={116}
|
||||
/>
|
||||
|
@ -655,8 +769,8 @@ exports[`Timeline should render with data 1`] = `
|
|||
"stroke": "#f5f5f5",
|
||||
}
|
||||
}
|
||||
x1={0}
|
||||
x2={0}
|
||||
x1={791.4677818871132}
|
||||
x2={791.4677818871132}
|
||||
y1={0}
|
||||
y2={116}
|
||||
/>
|
||||
|
@ -667,8 +781,8 @@ exports[`Timeline should render with data 1`] = `
|
|||
"stroke": "#f5f5f5",
|
||||
}
|
||||
}
|
||||
x1={0}
|
||||
x2={0}
|
||||
x1={879.408646541237}
|
||||
x2={879.408646541237}
|
||||
y1={0}
|
||||
y2={116}
|
||||
/>
|
||||
|
@ -684,8 +798,44 @@ exports[`Timeline should render with data 1`] = `
|
|||
"stroke": "#999999",
|
||||
}
|
||||
}
|
||||
x1={0}
|
||||
x2={0}
|
||||
x1={439.7043232706185}
|
||||
x2={439.7043232706185}
|
||||
y1={0}
|
||||
y2={116}
|
||||
/>
|
||||
<line
|
||||
className="rv-xy-plot__grid-lines__line"
|
||||
style={
|
||||
Object {
|
||||
"stroke": "#999999",
|
||||
}
|
||||
}
|
||||
x1={483.6747555976803}
|
||||
x2={483.6747555976803}
|
||||
y1={0}
|
||||
y2={116}
|
||||
/>
|
||||
<line
|
||||
className="rv-xy-plot__grid-lines__line"
|
||||
style={
|
||||
Object {
|
||||
"stroke": "#999999",
|
||||
}
|
||||
}
|
||||
x1={835.4382142141751}
|
||||
x2={835.4382142141751}
|
||||
y1={0}
|
||||
y2={116}
|
||||
/>
|
||||
<line
|
||||
className="rv-xy-plot__grid-lines__line"
|
||||
style={
|
||||
Object {
|
||||
"stroke": "#999999",
|
||||
}
|
||||
}
|
||||
x1={900}
|
||||
x2={900}
|
||||
y1={0}
|
||||
y2={116}
|
||||
/>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"width": 100,
|
||||
"width": 1000,
|
||||
"duration": 204683,
|
||||
"height": 116,
|
||||
"margins": {
|
||||
|
@ -8,5 +8,10 @@
|
|||
"right": 50,
|
||||
"bottom": 0
|
||||
},
|
||||
"animation": null
|
||||
"animation": null,
|
||||
"agentMarks": [
|
||||
{ "timeLabel": 100000, "name": "timeToFirstByte", "timeAxis": 100000 },
|
||||
{ "timeLabel": 110000, "name": "domInteractive", "timeAxis": 110000 },
|
||||
{ "timeLabel": 190000, "name": "domComplete", "timeAxis": 190000 }
|
||||
]
|
||||
}
|
||||
|
|
|
@ -23,8 +23,7 @@ class Timeline extends PureComponent {
|
|||
);
|
||||
|
||||
render() {
|
||||
const { width, duration, header } = this.props;
|
||||
|
||||
const { width, duration, header, agentMarks } = this.props;
|
||||
if (duration == null || !width) {
|
||||
return null;
|
||||
}
|
||||
|
@ -33,14 +32,19 @@ class Timeline extends PureComponent {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<TimelineAxis plotValues={plotValues} header={header} />
|
||||
<VerticalLines plotValues={plotValues} />
|
||||
<TimelineAxis
|
||||
plotValues={plotValues}
|
||||
agentMarks={agentMarks}
|
||||
header={header}
|
||||
/>
|
||||
<VerticalLines plotValues={plotValues} agentMarks={agentMarks} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Timeline.propTypes = {
|
||||
agentMarks: PropTypes.array,
|
||||
duration: PropTypes.number.isRequired,
|
||||
height: PropTypes.number.isRequired,
|
||||
header: PropTypes.node,
|
||||
|
|
16
x-pack/plugins/apm/public/utils/__test__/formatters.test.js
Normal file
16
x-pack/plugins/apm/public/utils/__test__/formatters.test.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { asTime } from '../formatters';
|
||||
|
||||
describe('formatters', () => {
|
||||
it('asTime', () => {
|
||||
expect(asTime(1000)).toBe('1 ms');
|
||||
expect(asTime(1000 * 1000)).toBe('1,000 ms');
|
||||
expect(asTime(1000 * 1000 * 10)).toBe('10,000 ms');
|
||||
expect(asTime(1000 * 1000 * 20)).toBe('20.0 s');
|
||||
});
|
||||
});
|
|
@ -7,7 +7,7 @@
|
|||
import { memoize } from 'lodash';
|
||||
import numeral from '@elastic/numeral';
|
||||
|
||||
const UNIT_CUT_OFF = 10 * 1000000;
|
||||
const UNIT_CUT_OFF = 10 * 1000000; // 10 seconds in microseconds
|
||||
|
||||
export function asSeconds(value, withUnit = true) {
|
||||
const formatted = asDecimal(value / 1000000);
|
||||
|
@ -34,6 +34,9 @@ export function timeUnit(max) {
|
|||
return max > UNIT_CUT_OFF ? 's' : 'ms';
|
||||
}
|
||||
|
||||
/*
|
||||
* value: time in microseconds
|
||||
*/
|
||||
export function asTime(value) {
|
||||
return getTimeFormatter(value)(value);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue