Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[APM] Add error rate chart to Errors overview and detail views #67327

Merged
merged 22 commits into from
Jun 18, 2020
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
581f5f7
creating error rate chart
cauemarcondes May 15, 2020
62e4a55
adding error line chart
cauemarcondes May 19, 2020
43ec10d
creating error rate chart
cauemarcondes May 25, 2020
9e59aca
using date_histogram
cauemarcondes May 26, 2020
517bec6
reapplying prettier style
cauemarcondes May 26, 2020
391883f
changing to theme color
cauemarcondes May 26, 2020
7bfbb7d
dont sync tooltips
cauemarcondes Jun 2, 2020
9bde4f8
adding avg on error charts
cauemarcondes Jun 3, 2020
34f0000
addressing pr comments
cauemarcondes Jun 5, 2020
8367fa7
Merge branch 'master' into error-rate-chart
elasticmachine Jun 5, 2020
f7d6e96
adding possibility to disable legend toggle
cauemarcondes Jun 8, 2020
536f848
Merge branch 'error-rate-chart' of github.com:cauemarcondes/kibana in…
cauemarcondes Jun 8, 2020
66cd570
Merge branch 'master' into error-rate-chart
elasticmachine Jun 8, 2020
519e8c5
removing x-axis ticks from histogram
cauemarcondes Jun 8, 2020
6a11358
return no percent when transaction count doesn return hits
cauemarcondes Jun 9, 2020
b14c2e7
addressing PR comments
cauemarcondes Jun 10, 2020
2ed28d5
Merge branch 'master' into error-rate-chart
elasticmachine Jun 10, 2020
a92d45a
Merge branch 'master' into error-rate-chart
elasticmachine Jun 12, 2020
336f898
Merge branch 'master' into error-rate-chart
elasticmachine Jun 15, 2020
fd78169
addressing PR comments
cauemarcondes Jun 15, 2020
5fb44ad
Merge branch 'master' into error-rate-chart
elasticmachine Jun 17, 2020
fa343da
returning null when there is no transaction count
cauemarcondes Jun 18, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
*/

import { EuiTitle } from '@elastic/eui';
import numeral from '@elastic/numeral';
import theme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
import { scaleUtc } from 'd3-scale';
import mean from 'lodash.mean';
import d3 from 'd3';
import React from 'react';
import React, { useMemo } from 'react';
import { asRelativeDateTimeRange } from '../../../../utils/formatters';
import { getTimezoneOffsetInMs } from '../../../shared/charts/CustomPlot/getTimezoneOffsetInMs';
// @ts-ignore
Expand All @@ -17,7 +20,7 @@ import { EmptyMessage } from '../../../shared/EmptyMessage';

interface IBucket {
key: number;
count: number;
count: number | undefined;
}

// TODO: cleanup duplication of this in distribution/get_distribution.ts (ErrorDistributionAPIResponse) and transactions/distribution/index.ts (TransactionDistributionAPIResponse)
Expand All @@ -30,7 +33,7 @@ interface IDistribution {
interface FormattedBucket {
x0: number;
x: number;
y: number;
y: number | undefined;
}

export function getFormattedBuckets(
Expand Down Expand Up @@ -64,7 +67,13 @@ export function ErrorDistribution({ distribution, title }: Props) {
distribution.bucketSize
);

if (!buckets || distribution.noHits) {
const average = useMemo(() => {
cauemarcondes marked this conversation as resolved.
Show resolved Hide resolved
const averageValue = buckets ? mean(buckets.map((bucket) => bucket.y)) : 0;
// 0a abbreviates large whole numbers with metric prefixes like: 1000 = 1k, 32000 = 32k, 1000000 = 1m
return numeral(averageValue).format('0a');
cauemarcondes marked this conversation as resolved.
Show resolved Hide resolved
}, [buckets]);

if (!buckets) {
return (
<EmptyMessage
heading={i18n.translate('xpack.apm.errorGroupDetails.noErrorsLabel', {
Expand Down Expand Up @@ -105,6 +114,16 @@ export function ErrorDistribution({ distribution, title }: Props) {
values: { occCount: value },
})
}
legends={[
{
color: theme.euiColorVis1,
legendValue: average,
title: i18n.translate('xpack.apm.errorGroupDetails.avgLabel', {
defaultMessage: 'Avg.',
}),
legendClickDisabled: true,
},
]}
/>
</div>
);
Expand Down
107 changes: 56 additions & 51 deletions x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import { ErrorDistribution } from './Distribution';
import { useLocation } from '../../../hooks/useLocation';
import { useUrlParams } from '../../../hooks/useUrlParams';
import { useTrackPageview } from '../../../../../observability/public';
import { callApmApi } from '../../../services/rest/createCallApmApi';
import { ErrorRateChart } from '../../shared/charts/ErrorRateChart';
import { ChartsSyncContextProvider } from '../../../context/ChartsSyncContext';

const Titles = styled.div`
margin-bottom: ${px(units.plus)};
Expand Down Expand Up @@ -61,49 +64,43 @@ export function ErrorGroupDetails() {
const { urlParams, uiFilters } = useUrlParams();
const { serviceName, start, end, errorGroupId } = urlParams;

const { data: errorGroupData } = useFetcher(
(callApmApi) => {
if (serviceName && start && end && errorGroupId) {
return callApmApi({
pathname: '/api/apm/services/{serviceName}/errors/{groupId}',
params: {
path: {
serviceName,
groupId: errorGroupId,
},
query: {
start,
end,
uiFilters: JSON.stringify(uiFilters),
},
const { data: errorGroupData } = useFetcher(() => {
if (serviceName && start && end && errorGroupId) {
return callApmApi({
pathname: '/api/apm/services/{serviceName}/errors/{groupId}',
params: {
path: {
serviceName,
groupId: errorGroupId,
},
});
}
},
[serviceName, start, end, errorGroupId, uiFilters]
);

const { data: errorDistributionData } = useFetcher(
(callApmApi) => {
if (serviceName && start && end && errorGroupId) {
return callApmApi({
pathname: '/api/apm/services/{serviceName}/errors/distribution',
params: {
path: {
serviceName,
},
query: {
start,
end,
groupId: errorGroupId,
uiFilters: JSON.stringify(uiFilters),
},
query: {
start,
end,
uiFilters: JSON.stringify(uiFilters),
},
});
}
},
[serviceName, start, end, errorGroupId, uiFilters]
);
},
});
}
}, [serviceName, start, end, errorGroupId, uiFilters]);

const { data: errorDistributionData } = useFetcher(() => {
if (serviceName && start && end && errorGroupId) {
return callApmApi({
pathname: '/api/apm/services/{serviceName}/errors/distribution',
params: {
path: {
serviceName,
},
query: {
start,
end,
groupId: errorGroupId,
uiFilters: JSON.stringify(uiFilters),
},
},
});
}
}, [serviceName, start, end, errorGroupId, uiFilters]);

useTrackPageview({ app: 'apm', path: 'error_group_details' });
useTrackPageview({ app: 'apm', path: 'error_group_details', delay: 15000 });
Expand Down Expand Up @@ -185,16 +182,24 @@ export function ErrorGroupDetails() {
</EuiText>
</Titles>
)}

<ErrorDistribution
distribution={errorDistributionData}
title={i18n.translate(
'xpack.apm.errorGroupDetails.occurrencesChartLabel',
{
defaultMessage: 'Occurrences',
}
)}
/>
<EuiFlexGroup gutterSize="s">
<ChartsSyncContextProvider>
<EuiFlexItem>
<ErrorDistribution
distribution={errorDistributionData}
title={i18n.translate(
'xpack.apm.errorGroupDetails.occurrencesChartLabel',
{
defaultMessage: 'Occurrences',
}
)}
/>
</EuiFlexItem>
<EuiFlexItem>
<ErrorRateChart />
</EuiFlexItem>
</ChartsSyncContextProvider>
</EuiFlexGroup>
</EuiPanel>
<EuiSpacer size="s" />
{showDetails && (
Expand Down
122 changes: 63 additions & 59 deletions x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,64 +13,61 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useMemo } from 'react';
import { useFetcher } from '../../../hooks/useFetcher';
import { ErrorDistribution } from '../ErrorGroupDetails/Distribution';
import { ErrorGroupList } from './List';
import { useUrlParams } from '../../../hooks/useUrlParams';
import { useTrackPageview } from '../../../../../observability/public';
import { PROJECTION } from '../../../../common/projections/typings';
import { useFetcher } from '../../../hooks/useFetcher';
import { useUrlParams } from '../../../hooks/useUrlParams';
import { callApmApi } from '../../../services/rest/createCallApmApi';
import { ErrorRateChart } from '../../shared/charts/ErrorRateChart';
import { LocalUIFilters } from '../../shared/LocalUIFilters';
import { ErrorDistribution } from '../ErrorGroupDetails/Distribution';
import { ErrorGroupList } from './List';
import { ChartsSyncContextProvider } from '../../../context/ChartsSyncContext';

const ErrorGroupOverview: React.FC = () => {
const { urlParams, uiFilters } = useUrlParams();

const { serviceName, start, end, sortField, sortDirection } = urlParams;

const { data: errorDistributionData } = useFetcher(
(callApmApi) => {
if (serviceName && start && end) {
return callApmApi({
pathname: '/api/apm/services/{serviceName}/errors/distribution',
params: {
path: {
serviceName,
},
query: {
start,
end,
uiFilters: JSON.stringify(uiFilters),
},
const { data: errorDistributionData } = useFetcher(() => {
if (serviceName && start && end) {
return callApmApi({
pathname: '/api/apm/services/{serviceName}/errors/distribution',
params: {
path: {
serviceName,
},
});
}
},
[serviceName, start, end, uiFilters]
);
query: {
start,
end,
uiFilters: JSON.stringify(uiFilters),
},
},
});
}
}, [serviceName, start, end, uiFilters]);

const { data: errorGroupListData } = useFetcher(
(callApmApi) => {
const normalizedSortDirection = sortDirection === 'asc' ? 'asc' : 'desc';
const { data: errorGroupListData } = useFetcher(() => {
const normalizedSortDirection = sortDirection === 'asc' ? 'asc' : 'desc';

if (serviceName && start && end) {
return callApmApi({
pathname: '/api/apm/services/{serviceName}/errors',
params: {
path: {
serviceName,
},
query: {
start,
end,
sortField,
sortDirection: normalizedSortDirection,
uiFilters: JSON.stringify(uiFilters),
},
if (serviceName && start && end) {
return callApmApi({
pathname: '/api/apm/services/{serviceName}/errors',
params: {
path: {
serviceName,
},
});
}
},
[serviceName, start, end, sortField, sortDirection, uiFilters]
);
query: {
start,
end,
sortField,
sortDirection: normalizedSortDirection,
uiFilters: JSON.stringify(uiFilters),
},
},
});
}
}, [serviceName, start, end, sortField, sortDirection, uiFilters]);

useTrackPageview({
app: 'apm',
Expand Down Expand Up @@ -102,20 +99,27 @@ const ErrorGroupOverview: React.FC = () => {
<LocalUIFilters {...localUIFiltersConfig} />
</EuiFlexItem>
<EuiFlexItem grow={7}>
<EuiFlexGroup>
<EuiFlexItem>
<EuiPanel>
<ErrorDistribution
distribution={errorDistributionData}
title={i18n.translate(
'xpack.apm.serviceDetails.metrics.errorOccurrencesChartTitle',
{
defaultMessage: 'Error occurrences',
}
)}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexGroup gutterSize="s">
cauemarcondes marked this conversation as resolved.
Show resolved Hide resolved
<ChartsSyncContextProvider>
<EuiFlexItem>
<EuiPanel>
<ErrorDistribution
distribution={errorDistributionData}
title={i18n.translate(
'xpack.apm.serviceDetails.metrics.errorOccurrencesChartTitle',
{
defaultMessage: 'Error occurrences',
}
)}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<EuiPanel>
<ErrorRateChart />
</EuiPanel>
</EuiFlexItem>
</ChartsSyncContextProvider>
</EuiFlexGroup>

<EuiSpacer size="s" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ export default function Legends({
return (
<Legend
key={i}
onClick={() => clickLegend(i)}
onClick={
serie.legendClickDisabled ? undefined : () => clickLegend(i)
}
disabled={seriesEnabledState[i]}
text={text}
color={serie.color}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ InnerCustomPlot.defaultProps = {
tickFormatX: undefined,
tickFormatY: (y) => y,
truncateLegends: false,
xAxisTickSizeOuter: 0,
};

export default makeWidthFlexible(InnerCustomPlot);
Loading