Skip to content

Commit f076f12

Browse files
[7.x] [RUM Dashboard] Added rum core web vitals (#75685) (#77152)
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
1 parent cebb761 commit f076f12

File tree

17 files changed

+740
-22
lines changed

17 files changed

+740
-22
lines changed

x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x-pack/plugins/apm/common/elasticsearch_fieldnames.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,9 @@ export const TRANSACTION_TIME_TO_FIRST_BYTE =
104104
'transaction.marks.agent.timeToFirstByte';
105105
export const TRANSACTION_DOM_INTERACTIVE =
106106
'transaction.marks.agent.domInteractive';
107+
108+
export const FCP_FIELD = 'transaction.marks.agent.firstContentfulPaint';
109+
export const LCP_FIELD = 'transaction.marks.agent.largestContentfulPaint';
110+
export const TBT_FIELD = 'transaction.experience.tbt';
111+
export const FID_FIELD = 'transaction.experience.fid';
112+
export const CLS_FIELD = 'transaction.experience.cls';

x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export function ClientMetrics() {
5656
<EuiFlexItem grow={false} style={STAT_STYLE}>
5757
<EuiStat
5858
titleSize="s"
59-
title={(data?.frontEnd?.value?.toFixed(2) ?? '-') + ' s'}
59+
title={((data?.frontEnd?.value ?? 0)?.toFixed(2) ?? '-') + ' s'}
6060
description={I18LABELS.frontEnd}
6161
isLoading={status !== 'success'}
6262
/>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import React from 'react';
8+
import { EuiFlexItem, EuiToolTip } from '@elastic/eui';
9+
import styled from 'styled-components';
10+
11+
const ColoredSpan = styled.div`
12+
height: 16px;
13+
width: 100%;
14+
cursor: pointer;
15+
`;
16+
17+
const getSpanStyle = (
18+
position: number,
19+
inFocus: boolean,
20+
hexCode: string,
21+
percentage: number
22+
) => {
23+
let first = position === 0 || percentage === 100;
24+
let last = position === 2 || percentage === 100;
25+
if (percentage === 100) {
26+
first = true;
27+
last = true;
28+
}
29+
30+
const spanStyle: any = {
31+
backgroundColor: hexCode,
32+
opacity: !inFocus ? 1 : 0.3,
33+
};
34+
let borderRadius = '';
35+
36+
if (first) {
37+
borderRadius = '4px 0 0 4px';
38+
}
39+
if (last) {
40+
borderRadius = '0 4px 4px 0';
41+
}
42+
if (first && last) {
43+
borderRadius = '4px';
44+
}
45+
spanStyle.borderRadius = borderRadius;
46+
47+
return spanStyle;
48+
};
49+
50+
export function ColorPaletteFlexItem({
51+
hexCode,
52+
inFocus,
53+
percentage,
54+
tooltip,
55+
position,
56+
}: {
57+
hexCode: string;
58+
position: number;
59+
inFocus: boolean;
60+
percentage: number;
61+
tooltip: string;
62+
}) {
63+
const spanStyle = getSpanStyle(position, inFocus, hexCode, percentage);
64+
65+
return (
66+
<EuiFlexItem key={hexCode} grow={false} style={{ width: percentage + '%' }}>
67+
<EuiToolTip content={tooltip}>
68+
<ColoredSpan style={spanStyle} />
69+
</EuiToolTip>
70+
</EuiFlexItem>
71+
);
72+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import {
8+
EuiFlexGroup,
9+
euiPaletteForStatus,
10+
EuiSpacer,
11+
EuiStat,
12+
} from '@elastic/eui';
13+
import React, { useState } from 'react';
14+
import { i18n } from '@kbn/i18n';
15+
import { PaletteLegends } from './PaletteLegends';
16+
import { ColorPaletteFlexItem } from './ColorPaletteFlexItem';
17+
import {
18+
AVERAGE_LABEL,
19+
GOOD_LABEL,
20+
LESS_LABEL,
21+
MORE_LABEL,
22+
POOR_LABEL,
23+
} from './translations';
24+
25+
export interface Thresholds {
26+
good: string;
27+
bad: string;
28+
}
29+
30+
interface Props {
31+
title: string;
32+
value: string;
33+
ranks?: number[];
34+
loading: boolean;
35+
thresholds: Thresholds;
36+
}
37+
38+
export function getCoreVitalTooltipMessage(
39+
thresholds: Thresholds,
40+
position: number,
41+
title: string,
42+
percentage: number
43+
) {
44+
const good = position === 0;
45+
const bad = position === 2;
46+
const average = !good && !bad;
47+
48+
return i18n.translate('xpack.apm.csm.dashboard.webVitals.palette.tooltip', {
49+
defaultMessage:
50+
'{percentage} % of users have {exp} experience because the {title} takes {moreOrLess} than {value}{averageMessage}.',
51+
values: {
52+
percentage,
53+
title: title?.toLowerCase(),
54+
exp: good ? GOOD_LABEL : bad ? POOR_LABEL : AVERAGE_LABEL,
55+
moreOrLess: bad || average ? MORE_LABEL : LESS_LABEL,
56+
value: good || average ? thresholds.good : thresholds.bad,
57+
averageMessage: average
58+
? i18n.translate('xpack.apm.rum.coreVitals.averageMessage', {
59+
defaultMessage: ' and less than {bad}',
60+
values: { bad: thresholds.bad },
61+
})
62+
: '',
63+
},
64+
});
65+
}
66+
67+
export function CoreVitalItem({
68+
loading,
69+
title,
70+
value,
71+
thresholds,
72+
ranks = [100, 0, 0],
73+
}: Props) {
74+
const palette = euiPaletteForStatus(3);
75+
76+
const [inFocusInd, setInFocusInd] = useState<number | null>(null);
77+
78+
const biggestValIndex = ranks.indexOf(Math.max(...ranks));
79+
80+
return (
81+
<>
82+
<EuiStat
83+
titleSize="s"
84+
title={value}
85+
description={title}
86+
titleColor={palette[biggestValIndex]}
87+
isLoading={loading}
88+
/>
89+
<EuiSpacer size="s" />
90+
<EuiFlexGroup
91+
gutterSize="none"
92+
alignItems="flexStart"
93+
style={{ maxWidth: 340 }}
94+
responsive={false}
95+
>
96+
{palette.map((hexCode, ind) => (
97+
<ColorPaletteFlexItem
98+
hexCode={hexCode}
99+
key={hexCode}
100+
position={ind}
101+
inFocus={inFocusInd !== ind && inFocusInd !== null}
102+
percentage={ranks[ind]}
103+
tooltip={getCoreVitalTooltipMessage(
104+
thresholds,
105+
ind,
106+
title,
107+
ranks[ind]
108+
)}
109+
/>
110+
))}
111+
</EuiFlexGroup>
112+
<EuiSpacer size="s" />
113+
<PaletteLegends
114+
ranks={ranks}
115+
thresholds={thresholds}
116+
title={title}
117+
onItemHover={(ind) => {
118+
setInFocusInd(ind);
119+
}}
120+
/>
121+
<EuiSpacer size="xl" />
122+
</>
123+
);
124+
}

0 commit comments

Comments
 (0)