From 4b36f3eb33eafd5b030531a23742b5dbf873989c Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Thu, 29 Oct 2020 16:31:14 -0500 Subject: [PATCH 1/4] Add error rate chart to overview Take most of the work directly from #80298 to add the error rate chart to the overview. Rename the existing chart that's on the transactions overview so it still keeps using the old chart for the time being. We don't want to mix chart types (react-vis + elastic-charts) on the same page becuase the interactions are different. We'll switch the transactions page to use elastic charts in a future PR. Hide the error rate chart on RUM services. --- .../components/app/ServiceMetrics/index.tsx | 2 +- .../app/ServiceNodeMetrics/index.tsx | 2 +- .../app/TransactionDetails/index.tsx | 2 +- .../app/TransactionOverview/index.tsx | 2 +- .../components/app/service_overview/index.tsx | 370 +++++++++--------- .../service_overview.test.tsx | 14 +- .../shared/charts/MetricsChart/index.tsx | 2 +- .../TransactionLineChart/index.tsx | 2 +- .../shared/charts/TransactionCharts/index.tsx | 2 +- .../shared/charts/annotations/index.tsx | 45 +++ .../shared/charts/chart_container.test.tsx | 34 ++ .../shared/charts/chart_container.tsx | 32 ++ .../index.tsx | 72 ++++ .../legacy.tsx} | 8 +- .../shared/charts/helper/helper.test.ts | 39 ++ .../components/shared/charts/helper/helper.ts | 35 ++ .../helper/{__test__ => }/timezone.test.ts | 2 +- .../shared/charts/line_chart/index.tsx | 137 +++++++ ...yncContext.tsx => charts_sync_context.tsx} | 30 +- .../apm/public/hooks/use_annotations.tsx | 38 ++ ...{useChartsSync.tsx => use_charts_sync.tsx} | 15 +- 21 files changed, 686 insertions(+), 199 deletions(-) create mode 100644 x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/index.tsx rename x-pack/plugins/apm/public/components/shared/charts/{ErroneousTransactionsRateChart/index.tsx => erroneous_transactions_rate_chart/legacy.tsx} (93%) create mode 100644 x-pack/plugins/apm/public/components/shared/charts/helper/helper.test.ts create mode 100644 x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts rename x-pack/plugins/apm/public/components/shared/charts/helper/{__test__ => }/timezone.test.ts (97%) create mode 100644 x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx rename x-pack/plugins/apm/public/context/{ChartsSyncContext.tsx => charts_sync_context.tsx} (79%) create mode 100644 x-pack/plugins/apm/public/hooks/use_annotations.tsx rename x-pack/plugins/apm/public/hooks/{useChartsSync.tsx => use_charts_sync.tsx} (61%) diff --git a/x-pack/plugins/apm/public/components/app/ServiceMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMetrics/index.tsx index 2fb500f3c99160..042752ef62f531 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMetrics/index.tsx @@ -15,7 +15,7 @@ import React, { useMemo } from 'react'; import { useServiceMetricCharts } from '../../../hooks/useServiceMetricCharts'; import { MetricsChart } from '../../shared/charts/MetricsChart'; import { useUrlParams } from '../../../hooks/useUrlParams'; -import { ChartsSyncContextProvider } from '../../../context/ChartsSyncContext'; +import { LegacyChartsSyncContextProvider as ChartsSyncContextProvider } from '../../../context/charts_sync_context'; import { Projection } from '../../../../common/projections'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceNodeMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceNodeMetrics/index.tsx index 84a1920d17fa89..566585c67e212e 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceNodeMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceNodeMetrics/index.tsx @@ -22,7 +22,7 @@ import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; import styled from 'styled-components'; import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes'; -import { ChartsSyncContextProvider } from '../../../context/ChartsSyncContext'; +import { LegacyChartsSyncContextProvider as ChartsSyncContextProvider } from '../../../context/charts_sync_context'; import { useAgentName } from '../../../hooks/useAgentName'; import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; import { useServiceMetricCharts } from '../../../hooks/useServiceMetricCharts'; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx index b79186a90cd1d3..efdd7b1f342210 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx @@ -24,7 +24,7 @@ import { TransactionCharts } from '../../shared/charts/TransactionCharts'; import { TransactionDistribution } from './Distribution'; import { WaterfallWithSummmary } from './WaterfallWithSummmary'; import { FETCH_STATUS } from '../../../hooks/useFetcher'; -import { ChartsSyncContextProvider } from '../../../context/ChartsSyncContext'; +import { LegacyChartsSyncContextProvider as ChartsSyncContextProvider } from '../../../context/charts_sync_context'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; import { fromQuery, toQuery } from '../../shared/Links/url_helpers'; diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx index 003df632d11b3c..5444d2d521f37e 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx @@ -22,7 +22,7 @@ import React, { useMemo } from 'react'; import { useLocation } from 'react-router-dom'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; -import { ChartsSyncContextProvider } from '../../../context/ChartsSyncContext'; +import { LegacyChartsSyncContextProvider as ChartsSyncContextProvider } from '../../../context/charts_sync_context'; import { IUrlParams } from '../../../context/UrlParamsContext/types'; import { useServiceTransactionTypes } from '../../../hooks/useServiceTransactionTypes'; import { useTransactionCharts } from '../../../hooks/useTransactionCharts'; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index 81f23b64275082..8f14571ff946e6 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -9,13 +9,16 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import styled from 'styled-components'; import { useTrackPageview } from '../../../../../observability/public'; +import { ChartsSyncContextProvider } from '../../../context/charts_sync_context'; +import { ErroneousTransactionsRateChart } from '../../shared/charts/erroneous_transactions_rate_chart'; import { ErrorOverviewLink } from '../../shared/Links/apm/ErrorOverviewLink'; import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink'; import { TransactionOverviewLink } from '../../shared/Links/apm/TransactionOverviewLink'; -const rowHeight = 310; -const latencyChartRowHeight = 230; - +// const rowHeight = 310; +// const latencyChartRowHeight = 230; +const rowHeight = 0; +const latencyChartRowHeight = 0; const Row = styled(EuiFlexItem)` height: ${rowHeight}px; `; @@ -39,208 +42,211 @@ export function ServiceOverview({ serviceName }: ServiceOverviewProps) { useTrackPageview({ app: 'apm', path: 'service_overview', delay: 15000 }); return ( - - - - - Search bar - - - Comparison picker - - - Date picker - - - - - - -

- {i18n.translate('xpack.apm.serviceOverview.latencyChartTitle', { - defaultMessage: 'Latency', - })} -

-
-
-
- - - - - -

- {i18n.translate( - 'xpack.apm.serviceOverview.trafficChartTitle', - { - defaultMessage: 'Traffic', - } - )} -

-
-
-
- - - - - -

- {i18n.translate( - 'xpack.apm.serviceOverview.transactionsTableTitle', - { - defaultMessage: 'Transactions', - } - )} -

-
-
- - + + + + + + Search bar + + + Comparison picker + + + Date picker + + + + + + +

+ {i18n.translate('xpack.apm.serviceOverview.latencyChartTitle', { + defaultMessage: 'Latency', + })} +

+
+
+
+ + + + + +

{i18n.translate( - 'xpack.apm.serviceOverview.transactionsTableLinkText', + 'xpack.apm.serviceOverview.trafficChartTitle', { - defaultMessage: 'View transactions', + defaultMessage: 'Traffic', } )} - - - - - - - - - - - - -

- {i18n.translate( - 'xpack.apm.serviceOverview.errorRateChartTitle', - { - defaultMessage: 'Error rate', - } - )} -

-
-
-
- - - - - -

+

+
+
+
+ + + + + +

+ {i18n.translate( + 'xpack.apm.serviceOverview.transactionsTableTitle', + { + defaultMessage: 'Transactions', + } + )} +

+
+
+ + {i18n.translate( - 'xpack.apm.serviceOverview.errorsTableTitle', + 'xpack.apm.serviceOverview.transactionsTableLinkText', { - defaultMessage: 'Errors', + defaultMessage: 'View transactions', } )} -

-
-
- - +
+
+
+
+
+
+
+ + + + + +

{i18n.translate( - 'xpack.apm.serviceOverview.errorsTableLinkText', + 'xpack.apm.serviceOverview.errorRateChartTitle', { - defaultMessage: 'View errors', + defaultMessage: 'Error rate', } )} - - - - - - - - - - - - - - -

+

+
+ +
+
+ + + + + +

+ {i18n.translate( + 'xpack.apm.serviceOverview.errorsTableTitle', + { + defaultMessage: 'Errors', + } + )} +

+
+
+ + {i18n.translate( - 'xpack.apm.serviceOverview.averageDurationBySpanTypeChartTitle', + 'xpack.apm.serviceOverview.errorsTableLinkText', { - defaultMessage: 'Average duration by span type', + defaultMessage: 'View errors', } )} -

-
-
-
- - - - - - - -

+ + + + + + + + + + + + + + +

+ {i18n.translate( + 'xpack.apm.serviceOverview.averageDurationBySpanTypeChartTitle', + { + defaultMessage: 'Average duration by span type', + } + )} +

+
+
+
+
+
+ + + + + +

+ {i18n.translate( + 'xpack.apm.serviceOverview.dependenciesTableTitle', + { + defaultMessage: 'Dependencies', + } + )} +

+
+
+ + {i18n.translate( - 'xpack.apm.serviceOverview.dependenciesTableTitle', + 'xpack.apm.serviceOverview.dependenciesTableLinkText', { - defaultMessage: 'Dependencies', + defaultMessage: 'View service map', } )} -

-
-
- - + + +
+
+
+
+ + + + + + +

+ {i18n.translate( + 'xpack.apm.serviceOverview.instancesLatencyDistributionChartTitle', + { + defaultMessage: 'Instances latency distribution', + } + )} +

+
+
+
+ + + +

{i18n.translate( - 'xpack.apm.serviceOverview.dependenciesTableLinkText', + 'xpack.apm.serviceOverview.instancesTableTitle', { - defaultMessage: 'View service map', + defaultMessage: 'Instances', } )} - - - - - - - - - - - - -

- {i18n.translate( - 'xpack.apm.serviceOverview.instancesLatencyDistributionChartTitle', - { - defaultMessage: 'Instances latency distribution', - } - )} -

-
-
-
- - - -

- {i18n.translate( - 'xpack.apm.serviceOverview.instancesTableTitle', - { - defaultMessage: 'Instances', - } - )} -

-
-
-
-
-
- +

+
+
+
+
+
+ + ); } diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx index 4e2063930a9c9d..210d3533038549 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx @@ -4,16 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { render } from '@testing-library/react'; import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; +import { CoreStart } from 'src/core/public'; +import { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public'; import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { renderWithTheme } from '../../../utils/testHelpers'; import { ServiceOverview } from './'; +const KibanaReactContext = createKibanaReactContext({ + usageCollection: { reportUiStats: () => {} }, +} as Partial); + function Wrapper({ children }: { children?: ReactNode }) { return ( - {children} + + {children} + ); } @@ -21,7 +29,7 @@ function Wrapper({ children }: { children?: ReactNode }) { describe('ServiceOverview', () => { it('renders', () => { expect(() => - render(, { + renderWithTheme(, { wrapper: Wrapper, }) ).not.toThrowError(); diff --git a/x-pack/plugins/apm/public/components/shared/charts/MetricsChart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/MetricsChart/index.tsx index 270ebd1c0830de..2f63a77132be98 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/MetricsChart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/MetricsChart/index.tsx @@ -19,7 +19,7 @@ import { GenericMetricsChart } from '../../../../../server/lib/metrics/transform import CustomPlot from '../CustomPlot'; import { Coordinate } from '../../../../../typings/timeseries'; import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; -import { useChartsSync } from '../../../../hooks/useChartsSync'; +import { useLegacyChartsSync as useChartsSync } from '../../../../hooks/use_charts_sync'; import { Maybe } from '../../../../../typings/common'; interface Props { diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/TransactionLineChart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/TransactionLineChart/index.tsx index 09e6b0e43945f1..2e4b51af00d6b7 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/TransactionLineChart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/TransactionLineChart/index.tsx @@ -6,7 +6,7 @@ import React, { useCallback } from 'react'; import { Coordinate, TimeSeries } from '../../../../../../typings/timeseries'; -import { useChartsSync } from '../../../../../hooks/useChartsSync'; +import { useLegacyChartsSync as useChartsSync } from '../../../../../hooks/use_charts_sync'; // @ts-expect-error import CustomPlot from '../../CustomPlot'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx index 0b741447f6fecd..b3c0c3b6de8577 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx @@ -26,7 +26,7 @@ import { IUrlParams } from '../../../../context/UrlParamsContext/types'; import { ITransactionChartData } from '../../../../selectors/chartSelectors'; import { asDecimal, tpmUnit } from '../../../../../common/utils/formatters'; import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; -import { ErroneousTransactionsRateChart } from '../ErroneousTransactionsRateChart'; +import { ErroneousTransactionsRateChart } from '../erroneous_transactions_rate_chart/legacy'; import { TransactionBreakdown } from '../../TransactionBreakdown'; import { getResponseTimeTickFormatter, diff --git a/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx new file mode 100644 index 00000000000000..683c66b2a96fee --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx @@ -0,0 +1,45 @@ +/* + * 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 { + AnnotationDomainTypes, + LineAnnotation, + Position, +} from '@elastic/charts'; +import { EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { asAbsoluteDateTime } from '../../../../../common/utils/formatters'; +import { useTheme } from '../../../../hooks/useTheme'; +import { useAnnotations } from '../../../../hooks/use_annotations'; + +export function Annotations() { + const { annotations } = useAnnotations(); + const theme = useTheme(); + + if (!annotations.length) { + return null; + } + + const color = theme.eui.euiColorSecondary; + + return ( + ({ + dataValue: annotation['@timestamp'], + header: asAbsoluteDateTime(annotation['@timestamp']), + details: `${i18n.translate('xpack.apm.chart.annotation.version', { + defaultMessage: 'Version', + })} ${annotation.text}`, + }))} + style={{ line: { strokeWidth: 1, stroke: color, opacity: 1 } }} + marker={} + markerPosition={Position.Top} + /> + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx new file mode 100644 index 00000000000000..409cb69575ca9c --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx @@ -0,0 +1,34 @@ +/* + * 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 { render } from '@testing-library/react'; +import React from 'react'; +import { ChartContainer } from './chart_container'; + +describe('ChartContainer', () => { + describe('when isLoading is true', () => { + it('shows loading the indicator', () => { + const component = render( + +
My amazing component
+
+ ); + + expect(component.getByTestId('loading')).toBeInTheDocument(); + }); + }); + + describe('when isLoading is false', () => { + it('does not show the loading indicator', () => { + const component = render( + +
My amazing component
+
+ ); + + expect(component.queryByTestId('loading')).not.toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx b/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx new file mode 100644 index 00000000000000..5ac5bc29ac1489 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx @@ -0,0 +1,32 @@ +/* + * 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 { EuiLoadingChart } from '@elastic/eui'; +import React from 'react'; + +interface Props { + isLoading: boolean; + height: number; + children: React.ReactNode; +} + +export function ChartContainer({ isLoading, children, height }: Props) { + if (isLoading) { + return ( +
+ +
+ ); + } + + return <>{children}; +} diff --git a/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/index.tsx new file mode 100644 index 00000000000000..dc6fecfb538b6a --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/index.tsx @@ -0,0 +1,72 @@ +/* + * 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 React from 'react'; +import { useParams } from 'react-router-dom'; +import { asPercent } from '../../../../../common/utils/formatters'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/useFetcher'; +import { useTheme } from '../../../../hooks/useTheme'; +import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { callApmApi } from '../../../../services/rest/createCallApmApi'; +import { LineChart } from '../line_chart'; + +const tickFormatY = (y?: number | null) => { + return asPercent(y || 0, 1); +}; + +export function ErroneousTransactionsRateChart() { + const theme = useTheme(); + const { serviceName } = useParams<{ serviceName?: string }>(); + const { urlParams, uiFilters } = useUrlParams(); + + const { start, end, transactionType, transactionName } = urlParams; + + const { data, status } = useFetcher(() => { + if (serviceName && start && end) { + return callApmApi({ + pathname: + '/api/apm/services/{serviceName}/transaction_groups/error_rate', + params: { + path: { + serviceName, + }, + query: { + start, + end, + transactionType, + transactionName, + uiFilters: JSON.stringify(uiFilters), + }, + }, + }); + } + }, [serviceName, start, end, uiFilters, transactionType, transactionName]); + + const errorRates = data?.transactionErrorRate || []; + + return ( + + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/legacy.tsx similarity index 93% rename from x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/index.tsx rename to x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/legacy.tsx index 8aec4184f924d6..29102f606414f9 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/legacy.tsx @@ -10,7 +10,7 @@ import { max } from 'lodash'; import React, { useCallback } from 'react'; import { useParams } from 'react-router-dom'; import { asPercent } from '../../../../../common/utils/formatters'; -import { useChartsSync } from '../../../../hooks/useChartsSync'; +import { useLegacyChartsSync as useChartsSync } from '../../../../hooks/use_charts_sync'; import { useFetcher } from '../../../../hooks/useFetcher'; import { useUrlParams } from '../../../../hooks/useUrlParams'; import { callApmApi } from '../../../../services/rest/createCallApmApi'; @@ -21,6 +21,12 @@ const tickFormatY = (y?: number | null) => { return asPercent(y || 0, 1); }; +/** + * "Legacy" version of this chart using react-vis charts. See index.tsx for the + * Elastic Charts version. + * + * This will be removed with #70290. + */ export function ErroneousTransactionsRateChart() { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/helper.test.ts b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.test.ts new file mode 100644 index 00000000000000..585eef546e7541 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.test.ts @@ -0,0 +1,39 @@ +/* + * 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 { onBrushEnd } from './helper'; +import { History } from 'history'; + +describe('Chart helper', () => { + describe('onBrushEnd', () => { + const history = ({ + push: jest.fn(), + location: { + search: '', + }, + } as unknown) as History; + it("doesn't push a new history when x is not defined", () => { + onBrushEnd({ x: undefined, history }); + expect(history.push).not.toBeCalled(); + }); + + it('pushes a new history with time range converted to ISO', () => { + onBrushEnd({ x: [1593409448167, 1593415727797], history }); + expect(history.push).toBeCalledWith({ + search: + 'rangeFrom=2020-06-29T05:44:08.167Z&rangeTo=2020-06-29T07:28:47.797Z', + }); + }); + + it('pushes a new history keeping current search', () => { + history.location.search = '?foo=bar'; + onBrushEnd({ x: [1593409448167, 1593415727797], history }); + expect(history.push).toBeCalledWith({ + search: + 'foo=bar&rangeFrom=2020-06-29T05:44:08.167Z&rangeTo=2020-06-29T07:28:47.797Z', + }); + }); + }); +}); diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts new file mode 100644 index 00000000000000..a9c1337feac993 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts @@ -0,0 +1,35 @@ +/* + * 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 { XYBrushArea } from '@elastic/charts'; +import { History } from 'history'; +import { fromQuery, toQuery } from '../../Links/url_helpers'; + +export const onBrushEnd = ({ + x, + history, +}: { + x: XYBrushArea['x']; + history: History; +}) => { + if (x) { + const start = x[0]; + const end = x[1]; + + const currentSearch = toQuery(history.location.search); + const nextSearch = { + rangeFrom: new Date(start).toISOString(), + rangeTo: new Date(end).toISOString(), + }; + history.push({ + ...history.location, + search: fromQuery({ + ...currentSearch, + ...nextSearch, + }), + }); + } +}; diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/__test__/timezone.test.ts b/x-pack/plugins/apm/public/components/shared/charts/helper/timezone.test.ts similarity index 97% rename from x-pack/plugins/apm/public/components/shared/charts/helper/__test__/timezone.test.ts rename to x-pack/plugins/apm/public/components/shared/charts/helper/timezone.test.ts index 0a6daf47b3ca67..3997448d17385d 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/helper/__test__/timezone.test.ts +++ b/x-pack/plugins/apm/public/components/shared/charts/helper/timezone.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import moment from 'moment-timezone'; -import { getDomainTZ, getTimeTicksTZ } from '../timezone'; +import { getDomainTZ, getTimeTicksTZ } from './timezone'; describe('Timezone helper', () => { let originalTimezone: moment.MomentZone | null; diff --git a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx new file mode 100644 index 00000000000000..659a7417e703f7 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx @@ -0,0 +1,137 @@ +/* + * 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 { + Axis, + Chart, + LegendItemListener, + LineSeries, + niceTimeFormatter, + Placement, + Position, + ScaleType, + Settings, + SettingsSpec, +} from '@elastic/charts'; +import moment from 'moment'; +import React, { useEffect } from 'react'; +import { useHistory } from 'react-router-dom'; +import { TimeSeries } from '../../../../../typings/timeseries'; +import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useChartsSync } from '../../../../hooks/use_charts_sync'; +import { unit } from '../../../../style/variables'; +import { Annotations } from '../annotations'; +import { ChartContainer } from '../chart_container'; +import { onBrushEnd } from '../helper/helper'; + +interface Props { + id: string; + isLoading: boolean; + onToggleLegend?: LegendItemListener; + tickFormatY: (y: number) => string; + timeseries: TimeSeries[]; +} + +const XY_HEIGHT = unit * 16; + +export function LineChart({ + id, + isLoading, + onToggleLegend, + tickFormatY, + timeseries, +}: Props) { + const history = useHistory(); + const chartRef = React.createRef(); + const { event, setEvent } = useChartsSync(); + const { urlParams } = useUrlParams(); + const { start, end } = urlParams; + + useEffect(() => { + if (event.chartId !== id && chartRef.current) { + chartRef.current.dispatchExternalPointerEvent(event); + } + }, [event, chartRef, id]); + + const min = moment.utc(start).valueOf(); + const max = moment.utc(end).valueOf(); + + const xFormatter = niceTimeFormatter([min, max]); + + const chartTheme: SettingsSpec['theme'] = { + lineSeriesStyle: { + point: { visible: false }, + line: { strokeWidth: 2 }, + }, + }; + + const isEmpty = timeseries + .map((serie) => serie.data) + .flat() + .every( + ({ y }: { x?: number | null; y?: number | null }) => + y === null || y === undefined + ); + + return ( +
+ + + onBrushEnd({ x, history })} + theme={chartTheme} + onPointerUpdate={(currEvent: any) => { + setEvent(currEvent); + }} + externalPointerEvents={{ + tooltip: { visible: true, placement: Placement.Bottom }, + }} + showLegend + showLegendExtra + legendPosition={Position.Bottom} + xDomain={{ min, max }} + onLegendItemClick={(legend) => { + if (onToggleLegend) { + onToggleLegend(legend); + } + }} + /> + + + + + + {!isEmpty && + timeseries.map((serie) => { + return ( + + ); + })} + + +
+ ); +} diff --git a/x-pack/plugins/apm/public/context/ChartsSyncContext.tsx b/x-pack/plugins/apm/public/context/charts_sync_context.tsx similarity index 79% rename from x-pack/plugins/apm/public/context/ChartsSyncContext.tsx rename to x-pack/plugins/apm/public/context/charts_sync_context.tsx index 7df35bc4432261..6f69ae097828bd 100644 --- a/x-pack/plugins/apm/public/context/ChartsSyncContext.tsx +++ b/x-pack/plugins/apm/public/context/charts_sync_context.tsx @@ -10,14 +10,18 @@ import { fromQuery, toQuery } from '../components/shared/Links/url_helpers'; import { useFetcher } from '../hooks/useFetcher'; import { useUrlParams } from '../hooks/useUrlParams'; -const ChartsSyncContext = React.createContext<{ +export const LegacyChartsSyncContext = React.createContext<{ hoverX: number | null; onHover: (hoverX: number) => void; onMouseLeave: () => void; onSelectionEnd: (range: { start: number; end: number }) => void; } | null>(null); -function ChartsSyncContextProvider({ children }: { children: ReactNode }) { +export function LegacyChartsSyncContextProvider({ + children, +}: { + children: ReactNode; +}) { const history = useHistory(); const [time, setTime] = useState(null); const { serviceName } = useParams<{ serviceName?: string }>(); @@ -79,7 +83,25 @@ function ChartsSyncContextProvider({ children }: { children: ReactNode }) { return { ...hoverXHandlers }; }, [history, time, data.annotations]); - return ; + return ; } -export { ChartsSyncContext, ChartsSyncContextProvider }; +export const ChartsSyncContext = React.createContext<{ + event: any; + setEvent: Function; +} | null>(null); + +export function ChartsSyncContextProvider({ + children, +}: { + children: ReactNode; +}) { + const [event, setEvent] = useState({}); + + return ( + + ); +} diff --git a/x-pack/plugins/apm/public/hooks/use_annotations.tsx b/x-pack/plugins/apm/public/hooks/use_annotations.tsx new file mode 100644 index 00000000000000..2b1c2bec52b3db --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_annotations.tsx @@ -0,0 +1,38 @@ +/* + * 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 { useParams } from 'react-router-dom'; +import { callApmApi } from '../services/rest/createCallApmApi'; +import { useFetcher } from './useFetcher'; +import { useUrlParams } from './useUrlParams'; + +const INITIAL_STATE = { annotations: [] }; + +export function useAnnotations() { + const { serviceName } = useParams<{ serviceName?: string }>(); + const { urlParams, uiFilters } = useUrlParams(); + const { start, end } = urlParams; + const { environment } = uiFilters; + + const { data = INITIAL_STATE } = useFetcher(() => { + if (start && end && serviceName) { + return callApmApi({ + pathname: '/api/apm/services/{serviceName}/annotation/search', + params: { + path: { + serviceName, + }, + query: { + start, + end, + environment, + }, + }, + }); + } + }, [start, end, environment, serviceName]); + + return data; +} diff --git a/x-pack/plugins/apm/public/hooks/useChartsSync.tsx b/x-pack/plugins/apm/public/hooks/use_charts_sync.tsx similarity index 61% rename from x-pack/plugins/apm/public/hooks/useChartsSync.tsx rename to x-pack/plugins/apm/public/hooks/use_charts_sync.tsx index 0416d2c0a7f187..52c7e4c1e3a31a 100644 --- a/x-pack/plugins/apm/public/hooks/useChartsSync.tsx +++ b/x-pack/plugins/apm/public/hooks/use_charts_sync.tsx @@ -5,7 +5,10 @@ */ import { useContext } from 'react'; -import { ChartsSyncContext } from '../context/ChartsSyncContext'; +import { + ChartsSyncContext, + LegacyChartsSyncContext, +} from '../context/charts_sync_context'; export function useChartsSync() { const context = useContext(ChartsSyncContext); @@ -16,3 +19,13 @@ export function useChartsSync() { return context; } + +export function useLegacyChartsSync() { + const context = useContext(LegacyChartsSyncContext); + + if (!context) { + throw new Error('Missing ChartsSync context provider'); + } + + return context; +} From 329c235bc04b7c445c3b01cc09afa6ea609c7ad7 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Thu, 29 Oct 2020 18:52:12 -0500 Subject: [PATCH 2/4] remove annotations --- .../shared/charts/line_chart/index.tsx | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx index 659a7417e703f7..52a12f7c9b4b1b 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx @@ -113,23 +113,20 @@ export function LineChart({ showGridLines /> - - - {!isEmpty && - timeseries.map((serie) => { - return ( - - ); - })} + {timeseries.map((serie) => { + return ( + + ); + })}
From 953e4773e6947ee51721ddf78e5f9c2239d8dc02 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Thu, 29 Oct 2020 19:32:22 -0500 Subject: [PATCH 3/4] Remove annotations; check for rum agent --- .../app/ServiceDetails/ServiceDetailTabs.tsx | 4 +- .../components/app/service_overview/index.tsx | 46 +++++++++++-------- .../shared/charts/annotations/index.tsx | 45 ------------------ .../shared/charts/line_chart/index.tsx | 1 - .../apm/public/hooks/use_annotations.tsx | 38 --------------- 5 files changed, 29 insertions(+), 105 deletions(-) delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx delete mode 100644 x-pack/plugins/apm/public/hooks/use_annotations.tsx diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx index d51e4a2dd3d7c2..625a8e73debc94 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx @@ -48,7 +48,9 @@ export function ServiceDetailTabs({ serviceName, tab }: Props) { })} ), - render: () => , + render: () => ( + + ), name: 'overview', }; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index 8f14571ff946e6..342152b572f1ec 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -9,16 +9,16 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import styled from 'styled-components'; import { useTrackPageview } from '../../../../../observability/public'; +import { isRumAgentName } from '../../../../common/agent_name'; import { ChartsSyncContextProvider } from '../../../context/charts_sync_context'; import { ErroneousTransactionsRateChart } from '../../shared/charts/erroneous_transactions_rate_chart'; import { ErrorOverviewLink } from '../../shared/Links/apm/ErrorOverviewLink'; import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink'; import { TransactionOverviewLink } from '../../shared/Links/apm/TransactionOverviewLink'; -// const rowHeight = 310; -// const latencyChartRowHeight = 230; -const rowHeight = 0; -const latencyChartRowHeight = 0; +const rowHeight = 310; +const latencyChartRowHeight = 230; + const Row = styled(EuiFlexItem)` height: ${rowHeight}px; `; @@ -34,10 +34,14 @@ const TableLinkFlexItem = styled(EuiFlexItem)` `; interface ServiceOverviewProps { + agentName?: string; serviceName: string; } -export function ServiceOverview({ serviceName }: ServiceOverviewProps) { +export function ServiceOverview({ + agentName, + serviceName, +}: ServiceOverviewProps) { useTrackPageview({ app: 'apm', path: 'service_overview' }); useTrackPageview({ app: 'apm', path: 'service_overview', delay: 15000 }); @@ -119,21 +123,23 @@ export function ServiceOverview({ serviceName }: ServiceOverviewProps) { - - - -

- {i18n.translate( - 'xpack.apm.serviceOverview.errorRateChartTitle', - { - defaultMessage: 'Error rate', - } - )} -

-
- -
-
+ {!isRumAgentName(agentName) && ( + + + +

+ {i18n.translate( + 'xpack.apm.serviceOverview.errorRateChartTitle', + { + defaultMessage: 'Error rate', + } + )} +

+
+ +
+
+ )} diff --git a/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx deleted file mode 100644 index 683c66b2a96fee..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 { - AnnotationDomainTypes, - LineAnnotation, - Position, -} from '@elastic/charts'; -import { EuiIcon } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { asAbsoluteDateTime } from '../../../../../common/utils/formatters'; -import { useTheme } from '../../../../hooks/useTheme'; -import { useAnnotations } from '../../../../hooks/use_annotations'; - -export function Annotations() { - const { annotations } = useAnnotations(); - const theme = useTheme(); - - if (!annotations.length) { - return null; - } - - const color = theme.eui.euiColorSecondary; - - return ( - ({ - dataValue: annotation['@timestamp'], - header: asAbsoluteDateTime(annotation['@timestamp']), - details: `${i18n.translate('xpack.apm.chart.annotation.version', { - defaultMessage: 'Version', - })} ${annotation.text}`, - }))} - style={{ line: { strokeWidth: 1, stroke: color, opacity: 1 } }} - marker={} - markerPosition={Position.Top} - /> - ); -} diff --git a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx index 52a12f7c9b4b1b..592890b35afe23 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx @@ -23,7 +23,6 @@ import { TimeSeries } from '../../../../../typings/timeseries'; import { useUrlParams } from '../../../../hooks/useUrlParams'; import { useChartsSync } from '../../../../hooks/use_charts_sync'; import { unit } from '../../../../style/variables'; -import { Annotations } from '../annotations'; import { ChartContainer } from '../chart_container'; import { onBrushEnd } from '../helper/helper'; diff --git a/x-pack/plugins/apm/public/hooks/use_annotations.tsx b/x-pack/plugins/apm/public/hooks/use_annotations.tsx deleted file mode 100644 index 2b1c2bec52b3db..00000000000000 --- a/x-pack/plugins/apm/public/hooks/use_annotations.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 { useParams } from 'react-router-dom'; -import { callApmApi } from '../services/rest/createCallApmApi'; -import { useFetcher } from './useFetcher'; -import { useUrlParams } from './useUrlParams'; - -const INITIAL_STATE = { annotations: [] }; - -export function useAnnotations() { - const { serviceName } = useParams<{ serviceName?: string }>(); - const { urlParams, uiFilters } = useUrlParams(); - const { start, end } = urlParams; - const { environment } = uiFilters; - - const { data = INITIAL_STATE } = useFetcher(() => { - if (start && end && serviceName) { - return callApmApi({ - pathname: '/api/apm/services/{serviceName}/annotation/search', - params: { - path: { - serviceName, - }, - query: { - start, - end, - environment, - }, - }, - }); - } - }, [start, end, environment, serviceName]); - - return data; -} From 13690b1126cd7aea35534c5beaa10c5d01c1f754 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Fri, 30 Oct 2020 10:45:40 -0500 Subject: [PATCH 4/4] Better tick formatting and chart container element --- .../shared/charts/chart_container.tsx | 29 ++--- .../index.tsx | 14 ++- .../shared/charts/line_chart/index.tsx | 115 ++++++++++-------- 3 files changed, 85 insertions(+), 73 deletions(-) diff --git a/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx b/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx index 5ac5bc29ac1489..a6f579308597ff 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx @@ -13,20 +13,17 @@ interface Props { } export function ChartContainer({ isLoading, children, height }: Props) { - if (isLoading) { - return ( -
- -
- ); - } - - return <>{children}; + return ( +
+ {isLoading && } + {children} +
+ ); } diff --git a/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/index.tsx index dc6fecfb538b6a..e08e8cec44a564 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/index.tsx @@ -14,9 +14,16 @@ import { useUrlParams } from '../../../../hooks/useUrlParams'; import { callApmApi } from '../../../../services/rest/createCallApmApi'; import { LineChart } from '../line_chart'; -const tickFormatY = (y?: number | null) => { +function yLabelFormat(y?: number | null) { return asPercent(y || 0, 1); -}; +} + +function yTickFormat(y?: number | null) { + return i18n.translate('xpack.apm.chart.averagePercentLabel', { + defaultMessage: '{y} (avg.)', + values: { y: yLabelFormat(y) }, + }); +} export function ErroneousTransactionsRateChart() { const theme = useTheme(); @@ -66,7 +73,8 @@ export function ErroneousTransactionsRateChart() { }), }, ]} - tickFormatY={tickFormatY} + yLabelFormat={yLabelFormat} + yTickFormat={yTickFormat} /> ); } diff --git a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx index 592890b35afe23..3f2a08ecb76415 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx @@ -30,8 +30,15 @@ interface Props { id: string; isLoading: boolean; onToggleLegend?: LegendItemListener; - tickFormatY: (y: number) => string; timeseries: TimeSeries[]; + /** + * Formatter for y-axis tick values + */ + yLabelFormat: (y: number) => string; + /** + * Formatter for legend and tooltip values + */ + yTickFormat: (y: number) => string; } const XY_HEIGHT = unit * 16; @@ -40,8 +47,9 @@ export function LineChart({ id, isLoading, onToggleLegend, - tickFormatY, timeseries, + yLabelFormat, + yTickFormat, }: Props) { const history = useHistory(); const chartRef = React.createRef(); @@ -76,58 +84,57 @@ export function LineChart({ ); return ( -
- - - onBrushEnd({ x, history })} - theme={chartTheme} - onPointerUpdate={(currEvent: any) => { - setEvent(currEvent); - }} - externalPointerEvents={{ - tooltip: { visible: true, placement: Placement.Bottom }, - }} - showLegend - showLegendExtra - legendPosition={Position.Bottom} - xDomain={{ min, max }} - onLegendItemClick={(legend) => { - if (onToggleLegend) { - onToggleLegend(legend); - } - }} - /> - - + + + onBrushEnd({ x, history })} + theme={chartTheme} + onPointerUpdate={(currEvent: any) => { + setEvent(currEvent); + }} + externalPointerEvents={{ + tooltip: { visible: true, placement: Placement.Bottom }, + }} + showLegend + showLegendExtra + legendPosition={Position.Bottom} + xDomain={{ min, max }} + onLegendItemClick={(legend) => { + if (onToggleLegend) { + onToggleLegend(legend); + } + }} + /> + + - {timeseries.map((serie) => { - return ( - - ); - })} - - -
+ {timeseries.map((serie) => { + return ( + + ); + })} +
+ ); }