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 2fb500f3c9916..042752ef62f53 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 84a1920d17fa8..566585c67e212 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 b79186a90cd1d..efdd7b1f34221 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 003df632d11b3..5444d2d521f37 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 81f23b6427508..8f14571ff946e 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 4e2063930a9c9..210d353303854 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 270ebd1c0830d..2f63a77132be9 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 09e6b0e43945f..2e4b51af00d6b 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 0b741447f6fec..b3c0c3b6de857 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 0000000000000..683c66b2a96fe --- /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 0000000000000..409cb69575ca9 --- /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 0000000000000..5ac5bc29ac148 --- /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 0000000000000..dc6fecfb538b6 --- /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 8aec4184f924d..29102f606414f 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 0000000000000..585eef546e754 --- /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 0000000000000..a9c1337feac99 --- /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 0a6daf47b3ca6..3997448d17385 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 0000000000000..659a7417e703f --- /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 7df35bc443226..6f69ae097828b 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 0000000000000..2b1c2bec52b3d --- /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 0416d2c0a7f18..52c7e4c1e3a31 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; +}