Skip to content

Commit

Permalink
[UX] Add empty states (#80904) (#81173)
Browse files Browse the repository at this point in the history
* Add empty state for user experience metrics.

* Add empty state for page load duration metrics.

* Add empty state for core web vitals.

* Fix bug injected by these changes.

* Add a test.
  • Loading branch information
justinkambic authored Oct 20, 2020
1 parent 0c9bc60 commit 597a851
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,24 @@ const ClFlexGroup = styled(EuiFlexGroup)`
}
`;

function formatTitle(unit: string, value?: number) {
if (typeof value === 'undefined') return I18LABELS.dataMissing;
return formatToSec(value, unit);
}

function PageViewsTotalTitle({ pageViews }: { pageViews?: number }) {
if (typeof pageViews === 'undefined') {
return <>{I18LABELS.dataMissing}</>;
}
return pageViews < 10000 ? (
<>{numeral(pageViews).format('0,0')}</>
) : (
<EuiToolTip content={numeral(pageViews).format('0,0')}>
<>{numeral(pageViews).format('0 a')}</>
</EuiToolTip>
);
}

export function ClientMetrics() {
const uxQuery = useUxQuery();

Expand Down Expand Up @@ -50,38 +68,28 @@ export function ClientMetrics() {

const STAT_STYLE = { width: '240px' };

const pageViewsTotal = data?.pageViews?.value ?? 0;

return (
<ClFlexGroup responsive={false}>
<EuiFlexItem grow={false} style={STAT_STYLE}>
<EuiStat
titleSize="l"
title={formatToSec(data?.backEnd?.value ?? 0, 'ms')}
title={formatTitle('ms', data?.backEnd?.value)}
description={I18LABELS.backEnd}
isLoading={status !== 'success'}
/>
</EuiFlexItem>
<EuiFlexItem grow={false} style={STAT_STYLE}>
<EuiStat
titleSize="l"
title={formatToSec(data?.frontEnd?.value ?? 0, 'ms')}
title={formatTitle('ms', data?.frontEnd?.value)}
description={I18LABELS.frontEnd}
isLoading={status !== 'success'}
/>
</EuiFlexItem>
<EuiFlexItem grow={false} style={STAT_STYLE}>
<EuiStat
titleSize="l"
title={
pageViewsTotal < 10000 ? (
numeral(pageViewsTotal).format('0,0')
) : (
<EuiToolTip content={numeral(pageViewsTotal).format('0,0')}>
<>{numeral(pageViewsTotal).format('0 a')}</>
</EuiToolTip>
)
}
title={<PageViewsTotalTitle pageViews={data?.pageViews?.value} />}
description={I18LABELS.pageViews}
isLoading={status !== 'success'}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import React from 'react';
import { EuiFlexItem, EuiStat, EuiFlexGroup } from '@elastic/eui';
import numeral from '@elastic/numeral';
import {
DATA_UNDEFINED_LABEL,
FCP_LABEL,
LONGEST_LONG_TASK,
NO_OF_LONG_TASK,
Expand Down Expand Up @@ -36,6 +37,11 @@ interface Props {
loading: boolean;
}

function formatTitle(unit: string, value?: number) {
if (typeof value === 'undefined') return DATA_UNDEFINED_LABEL;
return formatToSec(value, unit);
}

export function KeyUXMetrics({ data, loading }: Props) {
const uxQuery = useUxQuery();

Expand All @@ -62,39 +68,43 @@ export function KeyUXMetrics({ data, loading }: Props) {
<EuiFlexItem grow={false} style={STAT_STYLE}>
<EuiStat
titleSize="s"
title={formatToSec(data?.fcp, 'ms')}
title={formatTitle('ms', data?.fcp)}
description={FCP_LABEL}
isLoading={loading}
/>
</EuiFlexItem>
<EuiFlexItem grow={false} style={STAT_STYLE}>
<EuiStat
titleSize="s"
title={formatToSec(data?.tbt, 'ms')}
title={formatTitle('ms', data?.tbt)}
description={TBT_LABEL}
isLoading={loading}
/>
</EuiFlexItem>
<EuiFlexItem grow={false} style={STAT_STYLE}>
<EuiStat
titleSize="s"
title={numeral(longTaskData?.noOfLongTasks ?? 0).format('0,0')}
title={
longTaskData?.noOfLongTasks
? numeral(longTaskData.noOfLongTasks).format('0,0')
: DATA_UNDEFINED_LABEL
}
description={NO_OF_LONG_TASK}
isLoading={status !== 'success'}
/>
</EuiFlexItem>
<EuiFlexItem grow={false} style={STAT_STYLE}>
<EuiStat
titleSize="s"
title={formatToSec(longTaskData?.longestLongTask, 'ms')}
title={formatTitle('ms', longTaskData?.longestLongTask)}
description={LONGEST_LONG_TASK}
isLoading={status !== 'success'}
/>
</EuiFlexItem>
<EuiFlexItem grow={false} style={STAT_STYLE}>
<EuiStat
titleSize="s"
title={formatToSec(longTaskData?.sumOfLongTasks, 'ms')}
title={formatTitle('ms', longTaskData?.sumOfLongTasks)}
description={SUM_LONG_TASKS}
isLoading={status !== 'success'}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@

import { i18n } from '@kbn/i18n';

export const DATA_UNDEFINED_LABEL = i18n.translate(
'xpack.apm.rum.coreVitals.dataUndefined',
{
defaultMessage: 'N/A',
}
);

export const FCP_LABEL = i18n.translate('xpack.apm.rum.coreVitals.fcp', {
defaultMessage: 'First contentful paint',
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import { i18n } from '@kbn/i18n';

export const I18LABELS = {
dataMissing: i18n.translate('xpack.apm.rum.dashboard.dataMissing', {
defaultMessage: 'N/A',
}),
backEnd: i18n.translate('xpack.apm.rum.dashboard.backend', {
defaultMessage: 'Backend',
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,27 @@ describe('UXSection', () => {
expect(queryAllByText('View in app')).toEqual([]);
expect(getByText('elastic-co-frontend')).toBeInTheDocument();
});
it('shows empty state', () => {
jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({
data: undefined,
status: fetcherHook.FETCH_STATUS.SUCCESS,
refetch: jest.fn(),
});
const { getByText, queryAllByText, getAllByText } = render(
<UXSection
absoluteTime={{
start: moment('2020-06-29T11:38:23.747Z').valueOf(),
end: moment('2020-06-29T12:08:23.748Z').valueOf(),
}}
relativeTime={{ start: 'now-15m', end: 'now' }}
bucketSize="60s"
serviceName="elastic-co-frontend"
/>
);

expect(getByText('User Experience')).toBeInTheDocument();
expect(getAllByText('No data is available.')).toHaveLength(3);
expect(queryAllByText('View in app')).toEqual([]);
expect(getByText('elastic-co-frontend')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { EuiFlexGroup, EuiIconTip, euiPaletteForStatus, EuiSpacer, EuiStat } from '@elastic/eui';
import {
EuiCard,
EuiFlexGroup,
EuiIconTip,
euiPaletteForStatus,
EuiSpacer,
EuiStat,
} from '@elastic/eui';
import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { PaletteLegends } from './palette_legends';
Expand All @@ -14,6 +21,7 @@ import {
CV_GOOD_LABEL,
LESS_LABEL,
MORE_LABEL,
NO_DATA,
CV_POOR_LABEL,
IS_LABEL,
TAKES_LABEL,
Expand All @@ -26,7 +34,7 @@ export interface Thresholds {

interface Props {
title: string;
value: string;
value?: string;
ranks?: number[];
loading: boolean;
thresholds: Thresholds;
Expand Down Expand Up @@ -80,6 +88,9 @@ export function CoreVitalItem({

const biggestValIndex = ranks.indexOf(Math.max(...ranks));

if (value === undefined && ranks[0] === 100 && !loading) {
return <EuiCard title={title} isDisabled={true} description={NO_DATA} />;
}
return (
<>
<EuiStat
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ interface Props {
serviceName?: string;
}

function formatValue(value?: number) {
if (typeof value === 'undefined') {
return undefined;
}
return formatToSec(value, 'ms');
}

export function CoreVitals({ data, loading, displayServiceName, serviceName }: Props) {
const { lcp, lcpRanks, fid, fidRanks, cls, clsRanks } = data || {};

Expand All @@ -63,7 +70,7 @@ export function CoreVitals({ data, loading, displayServiceName, serviceName }: P
<EuiFlexItem style={{ flexBasis: 380 }}>
<CoreVitalItem
title={LCP_LABEL}
value={formatToSec(lcp, 'ms')}
value={formatValue(lcp)}
ranks={lcpRanks}
loading={loading}
thresholds={CoreVitalsThresholds.LCP}
Expand All @@ -73,7 +80,7 @@ export function CoreVitals({ data, loading, displayServiceName, serviceName }: P
<EuiFlexItem style={{ flexBasis: 380 }}>
<CoreVitalItem
title={FID_LABEL}
value={formatToSec(fid, 'ms')}
value={formatValue(fid)}
ranks={fidRanks}
loading={loading}
thresholds={CoreVitalsThresholds.FID}
Expand All @@ -83,7 +90,7 @@ export function CoreVitals({ data, loading, displayServiceName, serviceName }: P
<EuiFlexItem style={{ flexBasis: 380 }}>
<CoreVitalItem
title={CLS_LABEL}
value={cls ?? '0'}
value={cls}
ranks={clsRanks}
loading={loading}
thresholds={CoreVitalsThresholds.CLS}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

import { i18n } from '@kbn/i18n';

export const NO_DATA = i18n.translate('xpack.observability.ux.coreVitals.noData', {
defaultMessage: 'No data is available.',
});

export const LCP_LABEL = i18n.translate('xpack.observability.ux.coreVitals.lcp', {
defaultMessage: 'Largest contentful paint',
});
Expand Down

0 comments on commit 597a851

Please sign in to comment.