diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx index 582704a05354d..a17bf7e93e466 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx @@ -89,13 +89,7 @@ export function ErrorDistribution({ distribution, title }: Props) { showOverlappingTicks tickFormat={xFormatter} /> - `${value} occ.`} - /> + diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx index e45927626294a..bf1bda793179f 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx @@ -27,10 +27,11 @@ import { ValuesType } from 'utility-types'; import { useTheme } from '../../../../../../observability/public'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { TransactionDistributionAPIResponse } from '../../../../../server/lib/transactions/distribution'; +import type { TransactionDistributionAPIResponse } from '../../../../../server/lib/transactions/distribution'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { DistributionBucket } from '../../../../../server/lib/transactions/distribution/get_buckets'; +import type { DistributionBucket } from '../../../../../server/lib/transactions/distribution/get_buckets'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; +import { FETCH_STATUS } from '../../../../hooks/useFetcher'; import { unit } from '../../../../style/variables'; import { ChartContainer } from '../../../shared/charts/chart_container'; import { EmptyMessage } from '../../../shared/EmptyMessage'; @@ -109,21 +110,20 @@ const getFormatYLong = (transactionType: string | undefined) => (t: number) => { interface Props { distribution?: TransactionDistributionAPIResponse; urlParams: IUrlParams; - isLoading: boolean; + fetchStatus: FETCH_STATUS; bucketIndex: number; onBucketClick: ( bucket: ValuesType ) => void; } -export function TransactionDistribution(props: Props) { - const { - distribution, - urlParams: { transactionType }, - isLoading, - bucketIndex, - onBucketClick, - } = props; +export function TransactionDistribution({ + distribution, + urlParams: { transactionType }, + fetchStatus, + bucketIndex, + onBucketClick, +}: Props) { const theme = useTheme(); /* eslint-disable-next-line react-hooks/exhaustive-deps */ @@ -135,8 +135,12 @@ export function TransactionDistribution(props: Props) { const formatYLong = useCallback(getFormatYLong(transactionType), [ transactionType, ]); + // no data in response - if ((!distribution || distribution.noHits) && !isLoading) { + if ( + (!distribution || distribution.noHits) && + fetchStatus !== FETCH_STATUS.LOADING + ) { return ( formatYShort(value)} /> { - return `${value}`; - }} + tickFormat={(value: string) => value} minBarHeight={2} id="transactionDurationDistribution" name={(series: XYChartSeriesIdentifier) => { 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 412da777d62dd..e4c36b028e55c 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx @@ -125,10 +125,7 @@ export function TransactionDetails({ @@ -139,7 +136,7 @@ export function TransactionDetails({ { 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 4a5e7f3a4cc11..df9e673ed4847 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx @@ -22,6 +22,7 @@ import React, { useMemo } from 'react'; import { useLocation } from 'react-router-dom'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; +import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types'; import { IUrlParams } from '../../../context/UrlParamsContext/types'; import { useServiceTransactionTypes } from '../../../hooks/useServiceTransactionTypes'; import { useTransactionCharts } from '../../../hooks/useTransactionCharts'; @@ -32,12 +33,10 @@ import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; import { fromQuery, toQuery } from '../../shared/Links/url_helpers'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { TransactionTypeFilter } from '../../shared/LocalUIFilters/TransactionTypeFilter'; +import { Correlations } from '../Correlations'; import { TransactionList } from './TransactionList'; import { useRedirect } from './useRedirect'; -import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types'; import { UserExperienceCallout } from './user_experience_callout'; -import { Correlations } from '../Correlations'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; function getRedirectLocation({ urlParams, @@ -139,10 +138,7 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) { )} diff --git a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx index 16075d667819c..05cae589c19fc 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx @@ -14,12 +14,12 @@ import { ScaleType, Settings, } from '@elastic/charts'; -import { isEmpty } from 'lodash'; import moment from 'moment'; import React, { useEffect } from 'react'; import { useHistory } from 'react-router-dom'; import { asPercent } from '../../../../../common/utils/formatters'; import { TimeSeries } from '../../../../../typings/timeseries'; +import { FETCH_STATUS } from '../../../../hooks/useFetcher'; import { useUrlParams } from '../../../../hooks/useUrlParams'; import { useChartsSync as useChartsSync2 } from '../../../../hooks/use_charts_sync'; import { unit } from '../../../../style/variables'; @@ -30,14 +30,11 @@ import { onBrushEnd } from '../../charts/helper/helper'; const XY_HEIGHT = unit * 16; interface Props { - isLoading: boolean; + fetchStatus: FETCH_STATUS; timeseries?: TimeSeries[]; } -export function TransactionBreakdownGraph({ - isLoading, - timeseries = [], -}: Props) { +export function TransactionBreakdownGraph({ fetchStatus, timeseries }: Props) { const history = useHistory(); const chartRef = React.createRef(); const { event, setEvent } = useChartsSync2(); @@ -56,7 +53,11 @@ export function TransactionBreakdownGraph({ const xFormatter = niceTimeFormatter([min, max]); return ( - + onBrushEnd({ x, history })} @@ -87,10 +88,7 @@ export function TransactionBreakdownGraph({ - {isEmpty(timeseries) ? ( - // When timeseries is empty, loads an AreaSeries chart to show the default empty message. - - ) : ( + {timeseries?.length ? ( timeseries.map((serie) => { return ( ); }) + ) : ( + // When timeseries is empty, loads an AreaSeries chart to show the default empty message. + )} diff --git a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx index 1bea6f0840322..9b0c041aaf7b5 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx @@ -6,7 +6,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; import { useTransactionBreakdown } from '../../../hooks/useTransactionBreakdown'; import { TransactionBreakdownGraph } from './TransactionBreakdownGraph'; @@ -29,11 +28,7 @@ function TransactionBreakdown() { 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 835094eab8996..2a5948d0ebf0b 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 @@ -25,11 +25,12 @@ import { Coordinate } from '../../../../../typings/timeseries'; import { ChartsSyncContextProvider } from '../../../../context/charts_sync_context'; import { LicenseContext } from '../../../../context/LicenseContext'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; +import { FETCH_STATUS } from '../../../../hooks/useFetcher'; import { ITransactionChartData } from '../../../../selectors/chartSelectors'; import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; -import { TransactionErrorRateChart } from '../transaction_error_rate_chart/'; import { TransactionBreakdown } from '../../TransactionBreakdown'; import { LineChart } from '../line_chart'; +import { TransactionErrorRateChart } from '../transaction_error_rate_chart/'; import { getResponseTimeTickFormatter } from './helper'; import { MLHeader } from './ml_header'; import { useFormatter } from './use_formatter'; @@ -37,13 +38,13 @@ import { useFormatter } from './use_formatter'; interface TransactionChartProps { charts: ITransactionChartData; urlParams: IUrlParams; - isLoading: boolean; + fetchStatus: FETCH_STATUS; } export function TransactionCharts({ charts, urlParams, - isLoading, + fetchStatus, }: TransactionChartProps) { const getTPMFormatter = (t: number) => { return `${asDecimal(t)} ${tpmUnit(urlParams.transactionType)}`; @@ -81,7 +82,7 @@ export function TransactionCharts({ {tpmLabel(transactionType)} { - describe('when isLoading is true', () => { - it('shows loading the indicator', () => { - const component = render( - + describe('loading indicator', () => { + it('shows loading when status equals to Loading or Pending and has no data', () => { + [FETCH_STATUS.PENDING, FETCH_STATUS.LOADING].map((status) => { + const { queryAllByTestId } = render( + +
My amazing component
+
+ ); + + expect(queryAllByTestId('loading')[0]).toBeInTheDocument(); + }); + }); + it('does not show loading when status equals to Loading or Pending and has data', () => { + [FETCH_STATUS.PENDING, FETCH_STATUS.LOADING].map((status) => { + const { queryAllByText } = render( + +
My amazing component
+
+ ); + expect(queryAllByText('My amazing component')[0]).toBeInTheDocument(); + }); + }); + }); + + describe('failure indicator', () => { + it('shows failure message when status equals to Failure and has data', () => { + const { getByText } = render( +
My amazing component
); - - expect(component.getByTestId('loading')).toBeInTheDocument(); + expect( + getByText( + 'An error happened when trying to fetch data. Please try again' + ) + ).toBeInTheDocument(); + }); + it('shows failure message when status equals to Failure and has no data', () => { + const { getByText } = render( + +
My amazing component
+
+ ); + expect( + getByText( + 'An error happened when trying to fetch data. Please try again' + ) + ).toBeInTheDocument(); }); }); - describe('when isLoading is false', () => { - it('does not show the loading indicator', () => { - const component = render( - + describe('render component', () => { + it('shows children component when status Success and has data', () => { + const { getByText } = render( +
My amazing component
); - - expect(component.queryByTestId('loading')).not.toBeInTheDocument(); + expect(getByText('My amazing component')).toBeInTheDocument(); + }); + it('shows children component when status Success and has no data', () => { + const { getByText } = render( + +
My amazing component
+
+ ); + expect(getByText('My amazing component')).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 index a6f579308597f..b4486f1e9b94a 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 @@ -3,27 +3,56 @@ * 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 { EuiLoadingChart, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import React from 'react'; +import { FETCH_STATUS } from '../../../hooks/useFetcher'; interface Props { - isLoading: boolean; + hasData: boolean; + status: FETCH_STATUS; height: number; children: React.ReactNode; } -export function ChartContainer({ isLoading, children, height }: Props) { +export function ChartContainer({ children, height, status, hasData }: Props) { + if ( + !hasData && + (status === FETCH_STATUS.LOADING || status === FETCH_STATUS.PENDING) + ) { + return ; + } + + if (status === FETCH_STATUS.FAILURE) { + return ; + } + + return
{children}
; +} + +function LoadingChartPlaceholder({ height }: { height: number }) { return (
- {isLoading && } - {children} +
); } + +function FailedChartPlaceholder({ height }: { height: number }) { + return ( + + {i18n.translate('xpack.apm.chart.error', { + defaultMessage: + 'An error happened when trying to fetch data. Please try again', + })} + + ); +} 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 22f5299b38af5..507acc49d89db 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 @@ -20,16 +20,17 @@ import moment from 'moment'; import React, { useEffect } from 'react'; import { useHistory } from 'react-router-dom'; import { TimeSeries } from '../../../../../typings/timeseries'; +import { FETCH_STATUS } from '../../../../hooks/useFetcher'; 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'; -import { Annotations } from '../annotations'; interface Props { id: string; - isLoading: boolean; + fetchStatus: FETCH_STATUS; onToggleLegend?: LegendItemListener; timeseries: TimeSeries[]; /** @@ -47,7 +48,7 @@ const XY_HEIGHT = unit * 16; export function LineChart({ id, - isLoading, + fetchStatus, onToggleLegend, timeseries, yLabelFormat, @@ -87,7 +88,7 @@ export function LineChart({ ); return ( - + onBrushEnd({ x, history })} diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx index 2502321459930..2743d12a3eb04 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx @@ -4,14 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiTitle } from '@elastic/eui'; -import { EuiPanel } from '@elastic/eui'; - +import { EuiPanel, EuiTitle } from '@elastic/eui'; 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 { useFetcher } from '../../../../hooks/useFetcher'; import { useTheme } from '../../../../hooks/useTheme'; import { useUrlParams } from '../../../../hooks/useUrlParams'; import { callApmApi } from '../../../../services/rest/createCallApmApi'; @@ -74,11 +72,7 @@ export function TransactionErrorRateChart({ showAnnotations = true }: Props) { 0 ? buckets : [], }; }