From 374fea7b2a7601d41e94dc8ff8bdb9f86e7f8c3b Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 20 Oct 2020 10:14:42 -0400 Subject: [PATCH] [UX] Add empty states (#80904) * 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. --- .../app/RumDashboard/ClientMetrics/index.tsx | 34 ++++++++++++------- .../RumDashboard/UXMetrics/KeyUXMetrics.tsx | 20 ++++++++--- .../RumDashboard/UXMetrics/translations.ts | 7 ++++ .../app/RumDashboard/translations.ts | 3 ++ .../components/app/section/ux/index.test.tsx | 23 +++++++++++++ .../core_web_vitals/core_vital_item.tsx | 15 ++++++-- .../shared/core_web_vitals/index.tsx | 13 +++++-- .../shared/core_web_vitals/translations.ts | 4 +++ 8 files changed, 96 insertions(+), 23 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx index 310c01291aea4..0b4dcea5d12e0 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx @@ -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')} + ) : ( + + <>{numeral(pageViews).format('0 a')} + + ); +} + export function ClientMetrics() { const uxQuery = useUxQuery(); @@ -50,14 +68,12 @@ export function ClientMetrics() { const STAT_STYLE = { width: '240px' }; - const pageViewsTotal = data?.pageViews?.value ?? 0; - return ( @@ -65,7 +81,7 @@ export function ClientMetrics() { @@ -73,15 +89,7 @@ export function ClientMetrics() { - <>{numeral(pageViewsTotal).format('0 a')} - - ) - } + title={} description={I18LABELS.pageViews} isLoading={status !== 'success'} /> diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx index f30a3ea5fb2dd..793c9619edb3d 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx @@ -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, @@ -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(); @@ -62,7 +68,7 @@ export function KeyUXMetrics({ data, loading }: Props) { @@ -70,7 +76,7 @@ export function KeyUXMetrics({ data, loading }: Props) { @@ -78,7 +84,11 @@ export function KeyUXMetrics({ data, loading }: Props) { @@ -86,7 +96,7 @@ export function KeyUXMetrics({ data, loading }: Props) { @@ -94,7 +104,7 @@ export function KeyUXMetrics({ data, loading }: Props) { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/translations.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/translations.ts index e6d8f881bee57..8f3a71f669ecf 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/translations.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/translations.ts @@ -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', }); diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts index c8db011874a89..a8c4d67305c98 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts @@ -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', }), diff --git a/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx index cb23e4782a2d5..ef1820eaaeb3e 100644 --- a/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx @@ -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( + + ); + + 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(); + }); }); diff --git a/x-pack/plugins/observability/public/components/shared/core_web_vitals/core_vital_item.tsx b/x-pack/plugins/observability/public/components/shared/core_web_vitals/core_vital_item.tsx index e5b8d2c243617..0d0a388855ff2 100644 --- a/x-pack/plugins/observability/public/components/shared/core_web_vitals/core_vital_item.tsx +++ b/x-pack/plugins/observability/public/components/shared/core_web_vitals/core_vital_item.tsx @@ -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'; @@ -14,6 +21,7 @@ import { CV_GOOD_LABEL, LESS_LABEL, MORE_LABEL, + NO_DATA, CV_POOR_LABEL, IS_LABEL, TAKES_LABEL, @@ -26,7 +34,7 @@ export interface Thresholds { interface Props { title: string; - value: string; + value?: string; ranks?: number[]; loading: boolean; thresholds: Thresholds; @@ -80,6 +88,9 @@ export function CoreVitalItem({ const biggestValIndex = ranks.indexOf(Math.max(...ranks)); + if (value === undefined && ranks[0] === 100 && !loading) { + return ; + } return ( <>