[Uptime] Fix/85236 user experience display low values (#86026)
* add hasVitals prop to CoreVitalItem * pass hasVitals prop to CoreVitalsItem based on coreVitalPages * adjust criteria for displaying no core vital item data * add stories for CoreVitalItem edge cases * remove comment from core web vitals index page * update test comment in CoreVitalItem * adjust APM get_web_core_vitals endpoint to return a number for cls value, and adjust corresponding observability components * remove hasVitals from CoreVitalItem props and adjust storybook stories * add comment to EuiStat aria-label in CoreVitalItem * adjust CoreVitalItem tests * adjust APM KeyUXMetrics test * adjust APM get_web_core_vitals endpoint to return null for cls when cls is undefined * adjust unit and integration tests that rely on apm get_web_core_vitals * add comment in get_web_core_vitals * update CLS value in Observability core_web_vitals index * add withKibanaIntl to CoreVitalItem test to wrap in Intl Provider and KibanaReact provider * update CoreVitalItem test to use testing-library/react test_helper Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
f7961998d9
commit
cf6afe04ad
|
@ -23,7 +23,7 @@ describe('KeyUXMetrics', () => {
|
|||
<KeyUXMetrics
|
||||
loading={false}
|
||||
data={{
|
||||
cls: '0.01',
|
||||
cls: 0.01,
|
||||
fid: 6,
|
||||
lcp: 1701.1142857142856,
|
||||
tbt: 270.915,
|
||||
|
|
|
@ -132,7 +132,9 @@ export async function getWebCoreVitals({
|
|||
|
||||
return {
|
||||
coreVitalPages: coreVitalPages?.doc_count ?? 0,
|
||||
cls: cls?.values[pkey]?.toFixed(3) || null,
|
||||
/* Because cls is required in the type UXMetrics, and defined as number | null,
|
||||
* we need to default to null in the case where cls is undefined in order to satisfy the UXMetrics type */
|
||||
cls: cls?.values[pkey] ?? null,
|
||||
fid: fid?.values[pkey],
|
||||
lcp: lcp?.values[pkey],
|
||||
tbt: tbt?.values[pkey] ?? 0,
|
||||
|
|
|
@ -67,7 +67,7 @@ describe('UXSection', () => {
|
|||
expect(getByText('Largest contentful paint')).toBeInTheDocument();
|
||||
expect(getByText('1.94 s')).toBeInTheDocument();
|
||||
expect(getByText('14 ms')).toBeInTheDocument();
|
||||
expect(getByText('0.01')).toBeInTheDocument();
|
||||
expect(getByText('0.010')).toBeInTheDocument();
|
||||
|
||||
// LCP Rank Values
|
||||
expect(getByText('Good (65%)')).toBeInTheDocument();
|
||||
|
|
|
@ -9,7 +9,7 @@ import { UxFetchDataResponse } from '../../../../../typings';
|
|||
export const response: UxFetchDataResponse = {
|
||||
appLink: '/app/ux',
|
||||
coreWebVitals: {
|
||||
cls: '0.01',
|
||||
cls: 0.01,
|
||||
fid: 13.5,
|
||||
lcp: 1942.6666666666667,
|
||||
tbt: 281.55833333333334,
|
||||
|
|
|
@ -33,13 +33,26 @@ export default {
|
|||
],
|
||||
};
|
||||
|
||||
export function Basic() {
|
||||
export function NoDataAvailable() {
|
||||
return (
|
||||
<CoreVitalItem
|
||||
thresholds={{ good: '0.1', bad: '0.25' }}
|
||||
title={LCP_LABEL}
|
||||
value={null}
|
||||
loading={false}
|
||||
helpLabel={LCP_HELP_LABEL}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function OneHundredPercentGood() {
|
||||
return (
|
||||
<CoreVitalItem
|
||||
thresholds={{ good: '0.1', bad: '0.25' }}
|
||||
title={LCP_LABEL}
|
||||
value={'0.00s'}
|
||||
loading={false}
|
||||
ranks={[100, 0, 0]}
|
||||
helpLabel={LCP_HELP_LABEL}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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 { render } from '../../../utils/test_helper';
|
||||
import { CoreVitalItem } from './core_vital_item';
|
||||
import { NO_DATA } from './translations';
|
||||
|
||||
describe('CoreVitalItem', () => {
|
||||
const value = '0.005';
|
||||
const title = 'Cumulative Layout Shift';
|
||||
const thresholds = { bad: '0.25', good: '0.1' };
|
||||
const loading = false;
|
||||
const helpLabel = 'sample help label';
|
||||
|
||||
it('renders if value is truthy', () => {
|
||||
const { getByText } = render(
|
||||
<CoreVitalItem
|
||||
title={title}
|
||||
value={value}
|
||||
ranks={[85, 10, 5]}
|
||||
loading={loading}
|
||||
thresholds={thresholds}
|
||||
helpLabel={helpLabel}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(getByText(title)).toBeInTheDocument();
|
||||
expect(getByText(value)).toBeInTheDocument();
|
||||
expect(getByText('Good (85%)')).toBeInTheDocument();
|
||||
expect(getByText('Needs improvement (10%)')).toBeInTheDocument();
|
||||
expect(getByText('Poor (5%)')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders loading state when loading is truthy', () => {
|
||||
const { queryByText, getByText } = render(
|
||||
<CoreVitalItem
|
||||
title={title}
|
||||
value={value}
|
||||
ranks={[85, 10, 5]}
|
||||
loading={true}
|
||||
thresholds={thresholds}
|
||||
helpLabel={helpLabel}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(queryByText(value)).not.toBeInTheDocument();
|
||||
expect(getByText('--')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders no data UI if value is falsey and loading is falsey', () => {
|
||||
const { getByText } = render(
|
||||
<CoreVitalItem
|
||||
title={title}
|
||||
value={null}
|
||||
ranks={[85, 10, 5]}
|
||||
loading={loading}
|
||||
thresholds={thresholds}
|
||||
helpLabel={helpLabel}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(getByText(NO_DATA)).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -88,12 +88,14 @@ export function CoreVitalItem({
|
|||
|
||||
const biggestValIndex = ranks.indexOf(Math.max(...ranks));
|
||||
|
||||
if ((value === null || value !== undefined) && ranks[0] === 100 && !loading) {
|
||||
if (!value && !loading) {
|
||||
return <EuiCard title={title} isDisabled={true} description={NO_DATA} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiStat
|
||||
aria-label={`${title} ${value}`} // aria-label is required when passing a component, instead of a string, as the description
|
||||
titleSize="s"
|
||||
title={value ?? ''}
|
||||
description={
|
||||
|
|
|
@ -18,7 +18,7 @@ import { WebCoreVitalsTitle } from './web_core_vitals_title';
|
|||
import { ServiceName } from './service_name';
|
||||
|
||||
export interface UXMetrics {
|
||||
cls: string | null;
|
||||
cls: number | null;
|
||||
fid?: number | null;
|
||||
lcp?: number | null;
|
||||
tbt: number;
|
||||
|
@ -38,6 +38,13 @@ export function formatToSec(value?: number | string, fromUnit = 'MicroSec'): str
|
|||
return (valueInMs / 1000).toFixed(2) + ' s';
|
||||
}
|
||||
|
||||
function formatToMilliseconds(value?: number | null) {
|
||||
if (typeof value === 'undefined' || value === null) {
|
||||
return null;
|
||||
}
|
||||
return formatToSec(value, 'ms');
|
||||
}
|
||||
|
||||
const CoreVitalsThresholds = {
|
||||
LCP: { good: '2.5s', bad: '4.0s' },
|
||||
FID: { good: '100ms', bad: '300ms' },
|
||||
|
@ -53,13 +60,6 @@ interface Props {
|
|||
displayTrafficMetric?: boolean;
|
||||
}
|
||||
|
||||
function formatValue(value?: number | null) {
|
||||
if (typeof value === 'undefined' || value === null) {
|
||||
return null;
|
||||
}
|
||||
return formatToSec(value, 'ms');
|
||||
}
|
||||
|
||||
export function CoreVitals({
|
||||
data,
|
||||
loading,
|
||||
|
@ -85,7 +85,7 @@ export function CoreVitals({
|
|||
<EuiFlexItem style={{ flexBasis: 380 }}>
|
||||
<CoreVitalItem
|
||||
title={LCP_LABEL}
|
||||
value={formatValue(lcp)}
|
||||
value={formatToMilliseconds(lcp)}
|
||||
ranks={lcpRanks}
|
||||
loading={loading}
|
||||
thresholds={CoreVitalsThresholds.LCP}
|
||||
|
@ -95,7 +95,7 @@ export function CoreVitals({
|
|||
<EuiFlexItem style={{ flexBasis: 380 }}>
|
||||
<CoreVitalItem
|
||||
title={FID_LABEL}
|
||||
value={formatValue(fid)}
|
||||
value={formatToMilliseconds(fid)}
|
||||
ranks={fidRanks}
|
||||
loading={loading}
|
||||
thresholds={CoreVitalsThresholds.FID}
|
||||
|
@ -105,7 +105,7 @@ export function CoreVitals({
|
|||
<EuiFlexItem style={{ flexBasis: 380 }}>
|
||||
<CoreVitalItem
|
||||
title={CLS_LABEL}
|
||||
value={cls ?? null}
|
||||
value={cls?.toFixed(3) ?? null}
|
||||
ranks={clsRanks}
|
||||
loading={loading}
|
||||
thresholds={CoreVitalsThresholds.CLS}
|
||||
|
|
|
@ -270,7 +270,7 @@ describe('registerDataHandler', () => {
|
|||
title: 'User Experience',
|
||||
appLink: '/ux',
|
||||
coreWebVitals: {
|
||||
cls: '0.01',
|
||||
cls: 0.01,
|
||||
fid: 5,
|
||||
lcp: 1464.3333333333333,
|
||||
tbt: 232.92166666666665,
|
||||
|
@ -298,7 +298,7 @@ describe('registerDataHandler', () => {
|
|||
title: 'User Experience',
|
||||
appLink: '/ux',
|
||||
coreWebVitals: {
|
||||
cls: '0.01',
|
||||
cls: 0.01,
|
||||
fid: 5,
|
||||
lcp: 1464.3333333333333,
|
||||
tbt: 232.92166666666665,
|
||||
|
|
|
@ -49,7 +49,7 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext)
|
|||
|
||||
expectSnapshot(response.body).toMatchInline(`
|
||||
Object {
|
||||
"cls": "0.000",
|
||||
"cls": 0,
|
||||
"clsRanks": Array [
|
||||
100,
|
||||
0,
|
||||
|
|
Loading…
Reference in a new issue