Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[UX] Add percentile selector #78562

Merged
merged 18 commits into from
Sep 30, 2020
Merged
98 changes: 98 additions & 0 deletions x-pack/plugins/apm/common/ui_filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* 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 { i18n } from '@kbn/i18n';
import {
AGENT_NAME,
CLIENT_GEO_COUNTRY_ISO_CODE,
CONTAINER_ID,
HOST_NAME,
POD_NAME,
SERVICE_NAME,
SERVICE_VERSION,
TRANSACTION_RESULT,
TRANSACTION_URL,
USER_AGENT_DEVICE,
USER_AGENT_NAME,
USER_AGENT_OS,
} from './elasticsearch_fieldnames';

export const filtersByName = {
host: {
title: i18n.translate('xpack.apm.localFilters.titles.host', {
defaultMessage: 'Host',
}),
fieldName: HOST_NAME,
},
agentName: {
title: i18n.translate('xpack.apm.localFilters.titles.agentName', {
defaultMessage: 'Agent name',
}),
fieldName: AGENT_NAME,
},
containerId: {
title: i18n.translate('xpack.apm.localFilters.titles.containerId', {
defaultMessage: 'Container ID',
}),
fieldName: CONTAINER_ID,
},
podName: {
title: i18n.translate('xpack.apm.localFilters.titles.podName', {
defaultMessage: 'Kubernetes pod',
}),
fieldName: POD_NAME,
},
transactionResult: {
title: i18n.translate('xpack.apm.localFilters.titles.transactionResult', {
defaultMessage: 'Transaction result',
}),
fieldName: TRANSACTION_RESULT,
},
serviceVersion: {
title: i18n.translate('xpack.apm.localFilters.titles.serviceVersion', {
defaultMessage: 'Service version',
}),
fieldName: SERVICE_VERSION,
},
transactionUrl: {
title: i18n.translate('xpack.apm.localFilters.titles.transactionUrl', {
defaultMessage: 'Url',
}),
fieldName: TRANSACTION_URL,
},
browser: {
title: i18n.translate('xpack.apm.localFilters.titles.browser', {
defaultMessage: 'Browser',
}),
fieldName: USER_AGENT_NAME,
},
device: {
title: i18n.translate('xpack.apm.localFilters.titles.device', {
defaultMessage: 'Device',
}),
fieldName: USER_AGENT_DEVICE,
},
location: {
title: i18n.translate('xpack.apm.localFilters.titles.location', {
defaultMessage: 'Location',
}),
fieldName: CLIENT_GEO_COUNTRY_ISO_CODE,
},
os: {
title: i18n.translate('xpack.apm.localFilters.titles.os', {
defaultMessage: 'OS',
}),
fieldName: USER_AGENT_OS,
},
serviceName: {
title: i18n.translate('xpack.apm.localFilters.titles.serviceName', {
defaultMessage: 'Service name',
}),
fieldName: SERVICE_NAME,
},
};

export type LocalUIFilterName = keyof typeof filtersByName;
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ Feature: CSM Dashboard
When a user browses the APM UI application for RUM Data
Then should have correct client metrics

Scenario: Percentile select
When the user changes the selected percentile
Then it displays client metric related to that percentile

Scenario Outline: CSM page filters
When the user filters by "<filterName>"
Then it filters the client metrics "<filterName>"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 { When, Then } from 'cypress-cucumber-preprocessor/steps';
import { verifyClientMetrics } from './client_metrics_helper';
import { getDataTestSubj } from './utils';

When('the user changes the selected percentile', () => {
// wait for all loading to finish
cy.get('kbnLoadingIndicator').should('not.be.visible');

getDataTestSubj('uxPercentileSelect').click();

getDataTestSubj('p95Percentile').click();
});

Then(`it displays client metric related to that percentile`, () => {
const metrics = ['14 ms', '0.13 s', '55 '];

verifyClientMetrics(metrics, false);

// reset to median
getDataTestSubj('uxPercentileSelect').click();

getDataTestSubj('p50Percentile').click();
});
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Then(`it displays top pages in the suggestion popover`, () => {
listOfUrls.should('have.length', 5);

const actualUrlsText = [
'http://opbeans-node:3000/dashboardPage views: 17Page load duration: 109 ms ',
'http://opbeans-node:3000/dashboardPage views: 17Page load duration: 109 ms',
'http://opbeans-node:3000/ordersPage views: 14Page load duration: 72 ms',
];

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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 { DEFAULT_TIMEOUT } from './csm_dashboard';

export function getDataTestSubj(dataTestSubj: string) {
// wait for all loading to finish
cy.get('kbnLoadingIndicator').should('not.be.visible');

return cy.get(`[data-test-subj=${dataTestSubj}]`, DEFAULT_TIMEOUT);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import styled from 'styled-components';
import { useContext, useEffect } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiToolTip } from '@elastic/eui';
import { useFetcher } from '../../../../hooks/useFetcher';
import { useUrlParams } from '../../../../hooks/useUrlParams';
import { I18LABELS } from '../translations';
import { useUxQuery } from '../hooks/useUxQuery';
import { CsmSharedContext } from '../CsmSharedContext';

const ClFlexGroup = styled(EuiFlexGroup)`
Expand All @@ -22,29 +22,23 @@ const ClFlexGroup = styled(EuiFlexGroup)`
`;

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

const { start, end, searchTerm } = urlParams;
const uxQuery = useUxQuery();

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

const { setSharedData } = useContext(CsmSharedContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ export function PaletteLegends({
<StyledSpan darkMode={darkMode}>
<PaletteLegend color={color}>
<EuiText size="xs">
{labels[ind]} ({ranks?.[ind]}%)
<FormattedMessage
id="xpack.apm.rum.coreVitals.paletteLegend.rankPercentage"
defaultMessage="{labelsInd} ({ranksInd}%)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ export function PageLoadDistribution() {

const { data, status } = useFetcher(
(callApmApi) => {
if (start && end) {
const { serviceName } = uiFilters;

if (start && end && serviceName) {
return callApmApi({
pathname: '/api/apm/rum-client/page-load-distribution',
params: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ export function PageViewsTrend() {

const { data, status } = useFetcher(
(callApmApi) => {
if (start && end) {
const { serviceName } = uiFilters;

if (start && end && serviceName) {
return callApmApi({
pathname: '/api/apm/rum-client/page-view-trends',
params: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import React from 'react';
import { i18n } from '@kbn/i18n';
import { RumOverview } from '../RumDashboard';
import { RumHeader } from './RumHeader';
import { UserPercentile } from './UserPercentile';
import { CsmSharedContextProvider } from './CsmSharedContext';

export const UX_LABEL = i18n.translate('xpack.apm.ux.title', {
Expand All @@ -21,11 +22,14 @@ export function RumHome() {
<CsmSharedContextProvider>
<RumHeader>
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={false}>
<EuiFlexItem grow={true}>
<EuiTitle size="l">
<h1>{UX_LABEL}</h1>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<UserPercentile />
</EuiFlexItem>
Comment on lines +30 to +32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest aligning the percentile selector by the date picker. This is a global selector like the time range, so it feels more natural to place adjacent to that option.

94430494-4dfac600-0194-11eb-9925-54daf9c1f55a

cc @drewpost @katrin-freihofner

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could choose to add a prepended label in front of the selector which displays "Aggregation" or "Agg." shortened.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be nice to indicate that this is "active" when it isn't set to the default value of P50. This value gets persisted in the URL so someone could get linked to this state and be unaware.

</EuiFlexGroup>
</RumHeader>
<RumOverview />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { FormEvent, useRef, useState } from 'react';
import React, { FormEvent, SetStateAction, useRef, useState } from 'react';
import {
EuiButtonEmpty,
EuiFlexGroup,
Expand Down Expand Up @@ -33,6 +33,8 @@ interface Props {
onChange: (updatedOptions: UrlOption[]) => void;
searchValue: string;
onClose: () => void;
popoverIsOpen: boolean;
setPopoverIsOpen: React.Dispatch<SetStateAction<boolean>>;
}

export function SelectableUrlList({
Expand All @@ -43,8 +45,9 @@ export function SelectableUrlList({
onChange,
searchValue,
onClose,
popoverIsOpen,
setPopoverIsOpen,
}: Props) {
const [popoverIsOpen, setPopoverIsOpen] = useState(false);
const [popoverRef, setPopoverRef] = useState<HTMLElement | null>(null);
const [searchRef, setSearchRef] = useState<HTMLInputElement | null>(null);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { fromQuery, toQuery } from '../../../../shared/Links/url_helpers';
import { formatToSec } from '../../UXMetrics/KeyUXMetrics';
import { SelectableUrlList } from './SelectableUrlList';
import { UrlOption } from './RenderOption';
import { useUxQuery } from '../../hooks/useUxQuery';

interface Props {
onChange: (value: string[]) => void;
Expand All @@ -23,9 +24,10 @@ interface Props {
export function URLSearch({ onChange: onFilterChange }: Props) {
const history = useHistory();

const { urlParams, uiFilters } = useUrlParams();
const { uiFilters } = useUrlParams();

const [popoverIsOpen, setPopoverIsOpen] = useState(false);

const { start, end, serviceName } = urlParams;
const [searchValue, setSearchValue] = useState('');

const [debouncedValue, setDebouncedValue] = useState('');
Expand Down Expand Up @@ -54,17 +56,18 @@ export function URLSearch({ onChange: onFilterChange }: Props) {

const [checkedUrls, setCheckedUrls] = useState<string[]>([]);

const uxQuery = useUxQuery();

const { data, status } = useFetcher(
(callApmApi) => {
if (start && end && serviceName) {
if (uxQuery && popoverIsOpen) {
const { transactionUrl, ...restFilters } = uiFilters;

return callApmApi({
pathname: '/api/apm/rum-client/url-search',
params: {
query: {
start,
end,
...uxQuery,
uiFilters: JSON.stringify(restFilters),
urlQuery: searchValue,
},
Expand All @@ -73,7 +76,8 @@ export function URLSearch({ onChange: onFilterChange }: Props) {
}
return Promise.resolve(null);
},
[start, end, serviceName, uiFilters, searchValue]
// eslint-disable-next-line react-hooks/exhaustive-deps
[uxQuery, searchValue, popoverIsOpen]
);

useEffect(() => {
Expand Down Expand Up @@ -110,7 +114,9 @@ export function URLSearch({ onChange: onFilterChange }: Props) {
};

const onClose = () => {
onFilterChange(checkedUrls);
if (uiFilters.transactionUrl || checkedUrls.length > 0) {
onFilterChange(checkedUrls);
}
};

return (
Expand All @@ -126,6 +132,8 @@ export function URLSearch({ onChange: onFilterChange }: Props) {
onChange={onChange}
onClose={onClose}
searchValue={searchValue}
popoverIsOpen={popoverIsOpen}
setPopoverIsOpen={setPopoverIsOpen}
/>
</>
);
Expand Down
Loading