Skip to content

Commit

Permalink
Merge branch 'master' into actions/webhook-remove-header
Browse files Browse the repository at this point in the history
* master:
  [RUM Dashboard] User experience metrics (elastic#77384)
  Fixing service maps API test (elastic#77586)
  • Loading branch information
gmmorris committed Sep 16, 2020
2 parents 0e797f5 + 10b192b commit 21cb7a7
Show file tree
Hide file tree
Showing 23 changed files with 8,448 additions and 543 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,22 @@
*/
import * as React from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';

import { useFetcher } from '../../../../hooks/useFetcher';
import { useUrlParams } from '../../../../hooks/useUrlParams';
import { CLS_LABEL, FID_LABEL, LCP_LABEL } from './translations';
import { CoreVitalItem } from './CoreVitalItem';
import { UXMetrics } from '../UXMetrics';

const CoreVitalsThresholds = {
LCP: { good: '2.5s', bad: '4.0s' },
FID: { good: '100ms', bad: '300ms' },
CLS: { good: '0.1', bad: '0.25' },
};

export function CoreVitals() {
const { urlParams, uiFilters } = useUrlParams();

const { start, end } = urlParams;

const { data, status } = useFetcher(
(callApmApi) => {
const { serviceName } = uiFilters;
if (start && end && serviceName) {
return callApmApi({
pathname: '/api/apm/rum-client/web-core-vitals',
params: {
query: { start, end, uiFilters: JSON.stringify(uiFilters) },
},
});
}
return Promise.resolve(null);
},
[start, end, uiFilters]
);
interface Props {
data?: UXMetrics | null;
loading: boolean;
}

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

return (
Expand All @@ -47,7 +30,7 @@ export function CoreVitals() {
title={LCP_LABEL}
value={lcp ? lcp + 's' : '0'}
ranks={lcpRanks}
loading={status !== 'success'}
loading={loading}
thresholds={CoreVitalsThresholds.LCP}
/>
</EuiFlexItem>
Expand All @@ -56,7 +39,7 @@ export function CoreVitals() {
title={FID_LABEL}
value={fid ? fid + 's' : '0'}
ranks={fidRanks}
loading={status !== 'success'}
loading={loading}
thresholds={CoreVitalsThresholds.FID}
/>
</EuiFlexItem>
Expand All @@ -65,7 +48,7 @@ export function CoreVitals() {
title={CLS_LABEL}
value={cls ?? '0'}
ranks={clsRanks}
loading={status !== 'success'}
loading={loading}
thresholds={CoreVitalsThresholds.CLS}
/>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,27 @@ export const TBT_LABEL = i18n.translate('xpack.apm.rum.coreVitals.tbt', {
defaultMessage: 'Total blocking time',
});

export const NO_OF_LONG_TASK = i18n.translate(
'xpack.apm.rum.uxMetrics.noOfLongTasks',
{
defaultMessage: 'No. of long tasks',
}
);

export const LONGEST_LONG_TASK = i18n.translate(
'xpack.apm.rum.uxMetrics.longestLongTasks',
{
defaultMessage: 'Longest long task duration',
}
);

export const SUM_LONG_TASKS = i18n.translate(
'xpack.apm.rum.uxMetrics.sumLongTasks',
{
defaultMessage: 'Total long tasks duration',
}
);

export const POOR_LABEL = i18n.translate('xpack.apm.rum.coreVitals.poor', {
defaultMessage: 'a poor',
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { PageViewsTrend } from './PageViewsTrend';
import { PageLoadDistribution } from './PageLoadDistribution';
import { I18LABELS } from './translations';
import { VisitorBreakdown } from './VisitorBreakdown';
import { CoreVitals } from './CoreVitals';
import { UXMetrics } from './UXMetrics';
import { VisitorBreakdownMap } from './VisitorBreakdownMap';

export function RumDashboard() {
Expand All @@ -37,17 +37,7 @@ export function RumDashboard() {
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<EuiPanel>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={1} data-cy={`client-metrics`}>
<EuiTitle size="xs">
<h3>{I18LABELS.coreWebVitals}</h3>
</EuiTitle>
<EuiSpacer size="s" />
<CoreVitals />
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
<UXMetrics />
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup gutterSize="s" wrap>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* 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 { EuiFlexItem, EuiStat, EuiFlexGroup } from '@elastic/eui';
import { UXMetrics } from './index';
import {
FCP_LABEL,
LONGEST_LONG_TASK,
NO_OF_LONG_TASK,
SUM_LONG_TASKS,
TBT_LABEL,
} from '../CoreVitals/translations';
import { useUrlParams } from '../../../../hooks/useUrlParams';
import { useFetcher } from '../../../../hooks/useFetcher';

export function formatToSec(
value?: number | string,
fromUnit = 'MicroSec'
): string {
const valueInMs = Number(value ?? 0) / (fromUnit === 'MicroSec' ? 1000 : 1);

if (valueInMs < 1000) {
return valueInMs + ' ms';
}
return (valueInMs / 1000).toFixed(2) + ' s';
}
const STAT_STYLE = { width: '240px' };

interface Props {
data?: UXMetrics | null;
loading: boolean;
}

export function KeyUXMetrics({ data, loading }: Props) {
const { urlParams, uiFilters } = useUrlParams();

const { start, end, serviceName } = urlParams;

const { data: longTaskData, status } = useFetcher(
(callApmApi) => {
if (start && end && serviceName) {
return callApmApi({
pathname: '/api/apm/rum-client/long-task-metrics',
params: {
query: { start, end, uiFilters: JSON.stringify(uiFilters) },
},
});
}
return Promise.resolve(null);
},
[start, end, serviceName, uiFilters]
);

// Note: FCP value is in ms unit
return (
<EuiFlexGroup responsive={false}>
<EuiFlexItem grow={false} style={STAT_STYLE}>
<EuiStat
titleSize="s"
title={formatToSec(data?.fcp, 'ms')}
description={FCP_LABEL}
isLoading={loading}
/>
</EuiFlexItem>
<EuiFlexItem grow={false} style={STAT_STYLE}>
<EuiStat
titleSize="s"
title={formatToSec(data?.tbt)}
description={TBT_LABEL}
isLoading={loading}
/>
</EuiFlexItem>
<EuiFlexItem grow={false} style={STAT_STYLE}>
<EuiStat
titleSize="s"
title={longTaskData?.noOfLongTasks ?? 0}
description={NO_OF_LONG_TASK}
isLoading={status !== 'success'}
/>
</EuiFlexItem>
<EuiFlexItem grow={false} style={STAT_STYLE}>
<EuiStat
titleSize="s"
title={formatToSec(longTaskData?.longestLongTask)}
description={LONGEST_LONG_TASK}
isLoading={status !== 'success'}
/>
</EuiFlexItem>
<EuiFlexItem grow={false} style={STAT_STYLE}>
<EuiStat
titleSize="s"
title={formatToSec(longTaskData?.sumOfLongTasks)}
description={SUM_LONG_TASKS}
isLoading={status !== 'success'}
/>
</EuiFlexItem>
</EuiFlexGroup>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 { formatToSec } from '../KeyUXMetrics';

describe('FormatToSec', () => {
test('it returns the expected value', () => {
expect(formatToSec(3413000)).toStrictEqual('3.41 s');
expect(formatToSec(15548000)).toStrictEqual('15.55 s');
expect(formatToSec(1147.5, 'ms')).toStrictEqual('1.15 s');
expect(formatToSec(114, 'ms')).toStrictEqual('114 ms');
expect(formatToSec(undefined, 'ms')).toStrictEqual('0 ms');
expect(formatToSec(undefined)).toStrictEqual('0 ms');
expect(formatToSec('1123232')).toStrictEqual('1.12 s');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* 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 {
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
EuiPanel,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
import { I18LABELS } from '../translations';
import { CoreVitals } from '../CoreVitals';
import { KeyUXMetrics } from './KeyUXMetrics';
import { useUrlParams } from '../../../../hooks/useUrlParams';
import { useFetcher } from '../../../../hooks/useFetcher';

export interface UXMetrics {
cls: string;
fid: string;
lcp: string;
tbt: string;
fcp: number;
lcpRanks: number[];
fidRanks: number[];
clsRanks: number[];
}

export function UXMetrics() {
const { urlParams, uiFilters } = useUrlParams();

const { start, end } = urlParams;

const { data, status } = useFetcher(
(callApmApi) => {
const { serviceName } = uiFilters;
if (start && end && serviceName) {
return callApmApi({
pathname: '/api/apm/rum-client/web-core-vitals',
params: {
query: { start, end, uiFilters: JSON.stringify(uiFilters) },
},
});
}
return Promise.resolve(null);
},
[start, end, uiFilters]
);

return (
<EuiPanel>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={1} data-cy={`client-metrics`}>
<EuiTitle size="s">
<h2>{I18LABELS.userExperienceMetrics}</h2>
</EuiTitle>
<EuiSpacer size="s" />
<KeyUXMetrics data={data} loading={status !== 'success'} />
</EuiFlexItem>
</EuiFlexGroup>
<EuiHorizontalRule />

<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={1} data-cy={`client-metrics`}>
<EuiTitle size="xs">
<h3>{I18LABELS.coreWebVitals}</h3>
</EuiTitle>
<EuiSpacer size="s" />
<CoreVitals data={data} loading={status !== 'success'} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ export const I18LABELS = {
defaultMessage: 'Operating system',
}
),
userExperienceMetrics: i18n.translate('xpack.apm.rum.userExperienceMetrics', {
defaultMessage: 'User experience metrics',
}),
avgPageLoadDuration: i18n.translate(
'xpack.apm.rum.visitorBreakdownMap.avgPageLoadDuration',
{
Expand Down
Loading

0 comments on commit 21cb7a7

Please sign in to comment.