Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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