[ML] APM Correlations: Log log chart enhancements. (#113039)

- By clicking on a row in the analysis tables, the row gets selected/pinned as the one highlighted in the chart, allowing the user to investigate this particular result via hovering in the chart.
- A subtitle is added to the charts to clarify the chart type "Log-log plot for latency (x) by transactions (y) with overlapping bands" and lists the areas. Allows the user to see the full name of the highlighted entity because it could be cut off in the chart's native legend for longer field/value combinations.
- The area palette has been tweaked for higher contrasts, the error area color now matched the orange used in other charts for errors/failed transactions.
- Some visual tweaks like adding the non-transparent upper line for the areas to make sure there's a color in the chart itself that matches the legend colors:
- The trace samples tab now also shows an area with all failed transactions.
This commit is contained in:
Walter Rafelsberger 2021-10-19 19:59:01 +02:00 committed by GitHub
parent 36d128c3e7
commit 975beb125a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 430 additions and 158 deletions

View file

@ -22,6 +22,7 @@ interface CorrelationsTableProps<T extends FieldValuePair> {
significantTerms?: T[];
status: FETCH_STATUS;
percentageColumnName?: string;
setPinnedSignificantTerm?: (term: T | null) => void;
setSelectedSignificantTerm: (term: T | null) => void;
selectedTerm?: FieldValuePair;
onFilter?: () => void;
@ -33,6 +34,7 @@ interface CorrelationsTableProps<T extends FieldValuePair> {
export function CorrelationsTable<T extends FieldValuePair>({
significantTerms,
status,
setPinnedSignificantTerm,
setSelectedSignificantTerm,
columns,
selectedTerm,
@ -91,6 +93,11 @@ export function CorrelationsTable<T extends FieldValuePair>({
columns={columns}
rowProps={(term) => {
return {
onClick: () => {
if (setPinnedSignificantTerm) {
setPinnedSignificantTerm(term);
}
},
onMouseEnter: () => {
setSelectedSignificantTerm(term);
trackSelectSignificantCorrelationTerm();

View file

@ -18,6 +18,7 @@ import {
EuiTitle,
EuiBetaBadge,
EuiBadge,
EuiText,
EuiToolTip,
EuiSwitch,
EuiIconTip,
@ -26,6 +27,8 @@ import type { EuiTableSortingType } from '@elastic/eui/src/components/basic_tabl
import type { Direction } from '@elastic/eui/src/services/sort/sort_direction';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
enableInspectEsQueries,
useUiTracker,
@ -37,10 +40,13 @@ import {
APM_SEARCH_STRATEGIES,
DEFAULT_PERCENTILE_THRESHOLD,
} from '../../../../common/search_strategies/constants';
import { FieldStats } from '../../../../common/search_strategies/field_stats_types';
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
import { useLocalStorage } from '../../../hooks/useLocalStorage';
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
import { useSearchStrategy } from '../../../hooks/use_search_strategy';
import { useTheme } from '../../../hooks/use_theme';
import { ImpactBar } from '../../shared/ImpactBar';
import { push } from '../../shared/Links/url_helpers';
@ -58,10 +64,8 @@ import { CorrelationsLog } from './correlations_log';
import { CorrelationsEmptyStatePrompt } from './empty_state_prompt';
import { CrossClusterSearchCompatibilityWarning } from './cross_cluster_search_warning';
import { CorrelationsProgressControls } from './progress_controls';
import { useLocalStorage } from '../../../hooks/useLocalStorage';
import { useTheme } from '../../../hooks/use_theme';
import { useTransactionColors } from './use_transaction_colors';
import { CorrelationsContextPopover } from './context_popover';
import { FieldStats } from '../../../../common/search_strategies/field_stats_types';
import { OnAddFilter } from './context_popover/top_values';
export function FailedTransactionsCorrelations({
@ -69,6 +73,9 @@ export function FailedTransactionsCorrelations({
}: {
onFilter: () => void;
}) {
const euiTheme = useTheme();
const transactionColors = useTransactionColors();
const {
core: { notifications, uiSettings },
} = useApmPluginContext();
@ -96,18 +103,11 @@ export function FailedTransactionsCorrelations({
progress.isRunning
);
const [selectedSignificantTerm, setSelectedSignificantTerm] =
useState<FailedTransactionsCorrelation | null>(null);
const selectedTerm =
selectedSignificantTerm ?? response.failedTransactionsCorrelations?.[0];
const history = useHistory();
const [showStats, setShowStats] = useLocalStorage(
'apmFailedTransactionsShowAdvancedStats',
false
);
const euiTheme = useTheme();
const toggleShowStats = useCallback(() => {
setShowStats(!showStats);
@ -410,6 +410,30 @@ export function FailedTransactionsCorrelations({
[response.failedTransactionsCorrelations, sortField, sortDirection]
);
const [pinnedSignificantTerm, setPinnedSignificantTerm] =
useState<FailedTransactionsCorrelation | null>(null);
const [selectedSignificantTerm, setSelectedSignificantTerm] =
useState<FailedTransactionsCorrelation | null>(null);
const selectedTerm = useMemo(() => {
if (!correlationTerms) {
return;
} else if (selectedSignificantTerm) {
return correlationTerms?.find(
(h) =>
h.fieldName === selectedSignificantTerm.fieldName &&
h.fieldValue === selectedSignificantTerm.fieldValue
);
} else if (pinnedSignificantTerm) {
return correlationTerms.find(
(h) =>
h.fieldName === pinnedSignificantTerm.fieldName &&
h.fieldValue === pinnedSignificantTerm.fieldValue
);
}
return correlationTerms[0];
}, [correlationTerms, pinnedSignificantTerm, selectedSignificantTerm]);
const showCorrelationsTable =
progress.isRunning || correlationTerms.length > 0;
@ -497,6 +521,41 @@ export function FailedTransactionsCorrelations({
</EuiFlexItem>
</EuiFlexItem>
{selectedTerm && (
<EuiText color="subdued" size="xs">
<FormattedMessage
id="xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsChartDescription"
defaultMessage="Log-log plot for latency (x) by transactions (y) with overlapping bands for {br}{allTransactions}, {allFailedTransactions} and {focusTransaction}."
values={{
br: <br />,
allTransactions: (
<span style={{ color: transactionColors.ALL_TRANSACTIONS }}>
<FormattedMessage
id="xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsChartAllTransactions"
defaultMessage="all transactions"
/>
</span>
),
allFailedTransactions: (
<span
style={{ color: transactionColors.ALL_FAILED_TRANSACTIONS }}
>
<FormattedMessage
id="xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsChartAllFailedTransactions"
defaultMessage="all failed transactions"
/>
</span>
),
focusTransaction: (
<span style={{ color: transactionColors.FOCUS_TRANSACTION }}>
{selectedTerm?.fieldName}:{selectedTerm?.fieldValue}
</span>
),
}}
/>
</EuiText>
)}
<EuiSpacer size="s" />
<TransactionDistributionChart
@ -504,6 +563,11 @@ export function FailedTransactionsCorrelations({
markerValue={response.percentileThresholdValue ?? 0}
data={transactionDistributionChartData}
hasData={hasData}
palette={[
transactionColors.ALL_TRANSACTIONS,
transactionColors.ALL_FAILED_TRANSACTIONS,
transactionColors.FOCUS_TRANSACTION,
]}
status={status}
/>
@ -581,6 +645,7 @@ export function FailedTransactionsCorrelations({
status={
progress.isRunning ? FETCH_STATUS.LOADING : FETCH_STATUS.SUCCESS
}
setPinnedSignificantTerm={setPinnedSignificantTerm}
setSelectedSignificantTerm={setSelectedSignificantTerm}
selectedTerm={selectedTerm}
onTableChange={onTableChange}

View file

@ -15,6 +15,7 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
EuiText,
EuiTitle,
EuiToolTip,
} from '@elastic/eui';
@ -22,6 +23,7 @@ import { Direction } from '@elastic/eui/src/services/sort/sort_direction';
import { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
enableInspectEsQueries,
@ -34,6 +36,7 @@ import {
DEFAULT_PERCENTILE_THRESHOLD,
} from '../../../../common/search_strategies/constants';
import { LatencyCorrelation } from '../../../../common/search_strategies/latency_correlations/types';
import { FieldStats } from '../../../../common/search_strategies/field_stats_types';
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
@ -53,11 +56,13 @@ import { CorrelationsLog } from './correlations_log';
import { CorrelationsEmptyStatePrompt } from './empty_state_prompt';
import { CrossClusterSearchCompatibilityWarning } from './cross_cluster_search_warning';
import { CorrelationsProgressControls } from './progress_controls';
import { FieldStats } from '../../../../common/search_strategies/field_stats_types';
import { useTransactionColors } from './use_transaction_colors';
import { CorrelationsContextPopover } from './context_popover';
import { OnAddFilter } from './context_popover/top_values';
export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) {
const transactionColors = useTransactionColors();
const {
core: { notifications, uiSettings },
} = useApmPluginContext();
@ -98,19 +103,11 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) {
}
}, [progress.error, notifications.toasts]);
const [pinnedSignificantTerm, setPinnedSignificantTerm] =
useState<LatencyCorrelation | null>(null);
const [selectedSignificantTerm, setSelectedSignificantTerm] =
useState<LatencyCorrelation | null>(null);
const selectedHistogram = useMemo(
() =>
response.latencyCorrelations?.find(
(h) =>
h.fieldName === selectedSignificantTerm?.fieldName &&
h.fieldValue === selectedSignificantTerm?.fieldValue
) ?? response.latencyCorrelations?.[0],
[response.latencyCorrelations, selectedSignificantTerm]
);
const history = useHistory();
const trackApmEvent = useUiTracker({ app: 'apm' });
@ -270,6 +267,25 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) {
[response.latencyCorrelations, sortField, sortDirection]
);
const selectedHistogram = useMemo(() => {
if (!histogramTerms) {
return;
} else if (selectedSignificantTerm) {
return histogramTerms?.find(
(h) =>
h.fieldName === selectedSignificantTerm.fieldName &&
h.fieldValue === selectedSignificantTerm.fieldValue
);
} else if (pinnedSignificantTerm) {
return histogramTerms.find(
(h) =>
h.fieldName === pinnedSignificantTerm.fieldName &&
h.fieldValue === pinnedSignificantTerm.fieldValue
);
}
return histogramTerms[0];
}, [histogramTerms, pinnedSignificantTerm, selectedSignificantTerm]);
const showCorrelationsTable = progress.isRunning || histogramTerms.length > 0;
const showCorrelationsEmptyStatePrompt =
histogramTerms.length < 1 &&
@ -315,6 +331,31 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) {
</EuiFlexItem>
</EuiFlexGroup>
{selectedHistogram && (
<EuiText color="subdued" size="xs">
<FormattedMessage
id="xpack.apm.transactionDetails.tabs.latencyCorrelationsChartDescription"
defaultMessage="Log-log plot for latency (x) by transactions (y) with overlapping bands for {br}{allTransactions} and {focusTransaction}."
values={{
br: <br />,
allTransactions: (
<span style={{ color: transactionColors.ALL_TRANSACTIONS }}>
<FormattedMessage
id="xpack.apm.transactionDetails.tabs.latencyCorrelationsChartAllTransactions"
defaultMessage="all transactions"
/>
</span>
),
focusTransaction: (
<span style={{ color: transactionColors.FOCUS_TRANSACTION }}>
{selectedHistogram?.fieldName}:{selectedHistogram?.fieldValue}
</span>
),
}}
/>
</EuiText>
)}
<EuiSpacer size="s" />
<TransactionDistributionChart
@ -365,6 +406,7 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) {
status={
progress.isRunning ? FETCH_STATUS.LOADING : FETCH_STATUS.SUCCESS
}
setPinnedSignificantTerm={setPinnedSignificantTerm}
setSelectedSignificantTerm={setSelectedSignificantTerm}
selectedTerm={selectedHistogram}
onTableChange={onTableChange}

View file

@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useTheme } from '../../../hooks/use_theme';
export const useTransactionColors = () => {
const euiTheme = useTheme();
return {
ALL_TRANSACTIONS: euiTheme.eui.euiColorVis1,
ALL_FAILED_TRANSACTIONS: euiTheme.eui.euiColorVis7,
FOCUS_TRANSACTION: euiTheme.eui.euiColorVis2,
};
};

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useEffect } from 'react';
import React from 'react';
import { BrushEndListener, XYBrushEvent } from '@elastic/charts';
import {
EuiBadge,
@ -16,30 +16,27 @@ import {
EuiText,
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { useUiTracker } from '../../../../../../observability/public';
import { getDurationFormatter } from '../../../../../common/utils/formatters';
import { DEFAULT_PERCENTILE_THRESHOLD } from '../../../../../common/search_strategies/constants';
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
import { useUrlParams } from '../../../../context/url_params_context/use_url_params';
import { useApmParams } from '../../../../hooks/use_apm_params';
import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher';
import { useTimeRange } from '../../../../hooks/use_time_range';
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
import {
TransactionDistributionChart,
TransactionDistributionChartData,
} from '../../../shared/charts/transaction_distribution_chart';
import { isErrorMessage } from '../../correlations/utils/is_error_message';
import { TransactionDistributionChart } from '../../../shared/charts/transaction_distribution_chart';
import { useTransactionColors } from '../../correlations/use_transaction_colors';
import type { TabContentProps } from '../types';
import { useWaterfallFetcher } from '../use_waterfall_fetcher';
import { WaterfallWithSummary } from '../waterfall_with_summary';
import { useTransactionDistributionChartData } from './use_transaction_distribution_chart_data';
// Enforce min height so it's consistent across all tabs on the same level
// to prevent "flickering" behavior
const MIN_TAB_TITLE_HEIGHT = 56;
@ -71,15 +68,8 @@ export function TransactionDistribution({
selection,
traceSamples,
}: TransactionDistributionProps) {
const { serviceName, transactionType } = useApmServiceContext();
const {
core: { notifications },
} = useApmPluginContext();
const transactionColors = useTransactionColors();
const { urlParams } = useUrlParams();
const { transactionName } = urlParams;
const { waterfall, status: waterfallStatus } = useWaterfallFetcher();
const markerCurrentTransaction =
@ -99,68 +89,6 @@ export function TransactionDistribution({
}
);
const {
query: { kuery, environment, rangeFrom, rangeTo },
} = useApmParams('/services/{serviceName}/transactions/view');
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
const {
data = { log: [] },
status,
error,
} = useFetcher(
(callApmApi) => {
if (serviceName && environment && start && end) {
return callApmApi({
endpoint: 'GET /internal/apm/latency/overall_distribution',
params: {
query: {
serviceName,
transactionName,
transactionType,
kuery,
environment,
start,
end,
percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD,
},
},
});
}
},
[
serviceName,
transactionName,
transactionType,
kuery,
environment,
start,
end,
]
);
const overallHistogram =
data.overallHistogram === undefined && status !== FETCH_STATUS.LOADING
? []
: data.overallHistogram;
const hasData =
Array.isArray(overallHistogram) && overallHistogram.length > 0;
useEffect(() => {
if (isErrorMessage(error)) {
notifications.toasts.addDanger({
title: i18n.translate(
'xpack.apm.transactionDetails.distribution.errorTitle',
{
defaultMessage: 'An error occurred fetching the distribution',
}
),
text: error.toString(),
});
}
}, [error, notifications.toasts]);
const trackApmEvent = useUiTracker({ app: 'apm' });
const onTrackedChartSelection = (brushEvent: XYBrushEvent) => {
@ -173,18 +101,8 @@ export function TransactionDistribution({
trackApmEvent({ metric: 'transaction_distribution_chart_clear_selection' });
};
const transactionDistributionChartData: TransactionDistributionChartData[] =
[];
if (Array.isArray(overallHistogram)) {
transactionDistributionChartData.push({
id: i18n.translate(
'xpack.apm.transactionDistribution.chart.allTransactionsLabel',
{ defaultMessage: 'All transactions' }
),
histogram: overallHistogram,
});
}
const { chartData, hasData, percentileThresholdValue, status } =
useTransactionDistributionChartData();
return (
<div data-test-subj="apmTransactionDistributionTabContent">
@ -244,15 +162,46 @@ export function TransactionDistribution({
)}
</EuiFlexGroup>
<EuiText color="subdued" size="xs">
<FormattedMessage
id="xpack.apm.transactionDetails.tabs.transactionDistributionChartDescription"
defaultMessage="Log-log plot for latency (x) by transactions (y) with overlapping bands for {allTransactions} and {allFailedTransactions}."
values={{
allTransactions: (
<span style={{ color: transactionColors.ALL_TRANSACTIONS }}>
<FormattedMessage
id="xpack.apm.transactionDetails.tabs.transactionDistributionChartAllTransactions"
defaultMessage="all transactions"
/>
</span>
),
allFailedTransactions: (
<span
style={{ color: transactionColors.ALL_FAILED_TRANSACTIONS }}
>
<FormattedMessage
id="xpack.apm.transactionDetails.tabs.transactionDistributionChartAllFailedTransactions"
defaultMessage="all failed transactions"
/>
</span>
),
}}
/>
</EuiText>
<EuiSpacer size="s" />
<TransactionDistributionChart
data={transactionDistributionChartData}
data={chartData}
markerCurrentTransaction={markerCurrentTransaction}
markerPercentile={DEFAULT_PERCENTILE_THRESHOLD}
markerValue={data.percentileThresholdValue ?? 0}
markerValue={percentileThresholdValue ?? 0}
onChartSelection={onTrackedChartSelection as BrushEndListener}
hasData={hasData}
palette={[
transactionColors.ALL_TRANSACTIONS,
transactionColors.ALL_FAILED_TRANSACTIONS,
]}
selection={selection}
status={status}
/>

View file

@ -0,0 +1,187 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useEffect, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { DEFAULT_PERCENTILE_THRESHOLD } from '../../../../../common/search_strategies/constants';
import { RawSearchStrategyClientParams } from '../../../../../common/search_strategies/types';
import { EVENT_OUTCOME } from '../../../../../common/elasticsearch_fieldnames';
import { EventOutcome } from '../../../../../common/event_outcome';
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
import { useUrlParams } from '../../../../context/url_params_context/use_url_params';
import { useApmParams } from '../../../../hooks/use_apm_params';
import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher';
import { useTimeRange } from '../../../../hooks/use_time_range';
import type { TransactionDistributionChartData } from '../../../shared/charts/transaction_distribution_chart';
import { isErrorMessage } from '../../correlations/utils/is_error_message';
function hasRequiredParams(params: RawSearchStrategyClientParams) {
const { serviceName, environment, start, end } = params;
return serviceName && environment && start && end;
}
export const useTransactionDistributionChartData = () => {
const { serviceName, transactionType } = useApmServiceContext();
const {
core: { notifications },
} = useApmPluginContext();
const { urlParams } = useUrlParams();
const { transactionName } = urlParams;
const {
query: { kuery, environment, rangeFrom, rangeTo },
} = useApmParams('/services/{serviceName}/transactions/view');
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
const params = useMemo(
() => ({
serviceName,
transactionName,
transactionType,
kuery,
environment,
start,
end,
}),
[
serviceName,
transactionName,
transactionType,
kuery,
environment,
start,
end,
]
);
const {
// TODO The default object has `log: []` to retain compatibility with the shared search strategies code.
// Remove once the other tabs are migrated away from search strategies.
data: overallLatencyData = { log: [] },
status: overallLatencyStatus,
error: overallLatencyError,
} = useFetcher(
(callApmApi) => {
if (hasRequiredParams(params)) {
return callApmApi({
endpoint: 'POST /internal/apm/latency/overall_distribution',
params: {
body: {
...params,
percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD,
},
},
});
}
},
[params]
);
useEffect(() => {
if (isErrorMessage(overallLatencyError)) {
notifications.toasts.addDanger({
title: i18n.translate(
'xpack.apm.transactionDetails.distribution.latencyDistributionErrorTitle',
{
defaultMessage:
'An error occurred fetching the overall latency distribution.',
}
),
text: overallLatencyError.toString(),
});
}
}, [overallLatencyError, notifications.toasts]);
const overallLatencyHistogram =
overallLatencyData.overallHistogram === undefined &&
overallLatencyStatus !== FETCH_STATUS.LOADING
? []
: overallLatencyData.overallHistogram;
const hasData =
Array.isArray(overallLatencyHistogram) &&
overallLatencyHistogram.length > 0;
// TODO The default object has `log: []` to retain compatibility with the shared search strategies code.
// Remove once the other tabs are migrated away from search strategies.
const { data: errorHistogramData = { log: [] }, error: errorHistogramError } =
useFetcher(
(callApmApi) => {
if (hasRequiredParams(params)) {
return callApmApi({
endpoint: 'POST /internal/apm/latency/overall_distribution',
params: {
body: {
...params,
percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD,
termFilters: [
{
fieldName: EVENT_OUTCOME,
fieldValue: EventOutcome.failure,
},
],
},
},
});
}
},
[params]
);
useEffect(() => {
if (isErrorMessage(errorHistogramError)) {
notifications.toasts.addDanger({
title: i18n.translate(
'xpack.apm.transactionDetails.distribution.failedTransactionsLatencyDistributionErrorTitle',
{
defaultMessage:
'An error occurred fetching the failed transactions latency distribution.',
}
),
text: errorHistogramError.toString(),
});
}
}, [errorHistogramError, notifications.toasts]);
const transactionDistributionChartData: TransactionDistributionChartData[] =
[];
if (Array.isArray(overallLatencyHistogram)) {
transactionDistributionChartData.push({
id: i18n.translate(
'xpack.apm.transactionDistribution.chart.allTransactionsLabel',
{ defaultMessage: 'All transactions' }
),
histogram: overallLatencyHistogram,
});
}
if (Array.isArray(errorHistogramData.overallHistogram)) {
transactionDistributionChartData.push({
id: i18n.translate(
'xpack.apm.transactionDistribution.chart.allFailedTransactionsLabel',
{ defaultMessage: 'All failed transactions' }
),
histogram: errorHistogramData.overallHistogram,
});
}
return {
chartData: transactionDistributionChartData,
hasData,
percentileThresholdValue: overallLatencyData.percentileThresholdValue,
status: overallLatencyStatus,
};
};

View file

@ -29,8 +29,8 @@ describe('TransactionDistributionChart', () => {
{ doc_count: 10 },
{ doc_count: 10 },
{ doc_count: 0.0001 },
{ doc_count: 0 },
{ doc_count: 0 },
{ doc_count: 0.0001 },
{ doc_count: 0.0001 },
{ doc_count: 10 },
{ doc_count: 10 },
{ doc_count: 0.0001 },

View file

@ -51,6 +51,7 @@ interface TransactionDistributionChartProps {
markerValue: number;
markerPercentile: number;
onChartSelection?: BrushEndListener;
palette?: string[];
selection?: [number, number];
status: FETCH_STATUS;
}
@ -77,13 +78,10 @@ const CHART_PLACEHOLDER_VALUE = 0.0001;
// Elastic charts will show any lone bin (i.e. a populated bin followed by empty bin)
// as a circular marker instead of a bar
// This provides a workaround by making the next bin not empty
// TODO Find a way to get rid of this workaround since it alters original values of the data.
export const replaceHistogramDotsWithBars = (histogramItems: HistogramItem[]) =>
histogramItems.reduce((histogramItem, _, i) => {
if (
histogramItem[i - 1]?.doc_count > 0 &&
histogramItem[i - 1]?.doc_count !== CHART_PLACEHOLDER_VALUE &&
histogramItem[i].doc_count === 0
) {
if (histogramItem[i].doc_count === 0) {
histogramItem[i].doc_count = CHART_PLACEHOLDER_VALUE;
}
return histogramItem;
@ -102,16 +100,16 @@ export function TransactionDistributionChart({
markerValue,
markerPercentile,
onChartSelection,
palette,
selection,
status,
}: TransactionDistributionChartProps) {
const chartTheme = useChartTheme();
const euiTheme = useTheme();
const areaSeriesColors = [
const areaSeriesColors = palette ?? [
euiTheme.eui.euiColorVis1,
euiTheme.eui.euiColorVis2,
euiTheme.eui.euiColorVis5,
];
const annotationsDataValues: LineAnnotationDatum[] = [
@ -136,7 +134,7 @@ export function TransactionDistributionChart({
) ?? 0;
const yTicks = Math.ceil(Math.log10(yMax));
const yAxisDomain = {
min: 0.9,
min: 0.5,
max: Math.pow(10, yTicks),
};
@ -171,7 +169,7 @@ export function TransactionDistributionChart({
},
areaSeriesStyle: {
line: {
visible: false,
visible: true,
},
},
axes: {
@ -186,7 +184,7 @@ export function TransactionDistributionChart({
},
},
}}
showLegend
showLegend={true}
legendPosition={Position.Bottom}
onBrushEnd={onChartSelection}
/>
@ -238,7 +236,10 @@ export function TransactionDistributionChart({
/>
<Axis
id="x-axis"
title=""
title={i18n.translate(
'xpack.apm.transactionDistribution.chart.latencyLabel',
{ defaultMessage: 'Latency' }
)}
position={Position.Bottom}
tickFormat={xAxisTickFormat}
gridLine={{ visible: false }}
@ -248,7 +249,7 @@ export function TransactionDistributionChart({
domain={yAxisDomain}
title={i18n.translate(
'xpack.apm.transactionDistribution.chart.numberOfTransactionsLabel',
{ defaultMessage: '# transactions' }
{ defaultMessage: 'Transactions' }
)}
position={Position.Left}
ticks={yTicks}

View file

@ -31,7 +31,7 @@ export async function getOverallLatencyDistribution(
log: [],
};
const { setup, ...rawParams } = options;
const { setup, termFilters, ...rawParams } = options;
const { apmEventClient } = setup;
const params = {
// pass on an empty index because we're using only the body attribute
@ -86,7 +86,11 @@ export async function getOverallLatencyDistribution(
// #3: get histogram chart data
const { body: transactionDurationRangesRequestBody } =
getTransactionDurationRangesRequest(params, histogramRangeSteps);
getTransactionDurationRangesRequest(
params,
histogramRangeSteps,
termFilters
);
const transactionDurationRangesResponse = (await apmEventClient.search(
'get_transaction_duration_ranges',

View file

@ -5,11 +5,17 @@
* 2.0.
*/
import { Setup } from '../helpers/setup_request';
import { CorrelationsOptions } from '../search_strategies/queries/get_filters';
import type {
FieldValuePair,
SearchStrategyClientParams,
} from '../../../common/search_strategies/types';
export interface OverallLatencyDistributionOptions extends CorrelationsOptions {
import { Setup } from '../helpers/setup_request';
export interface OverallLatencyDistributionOptions
extends SearchStrategyClientParams {
percentileThreshold: number;
termFilters?: FieldValuePair[];
setup: Setup;
}

View file

@ -15,16 +15,7 @@ import {
PROCESSOR_EVENT,
} from '../../../../common/elasticsearch_fieldnames';
import { ProcessorEvent } from '../../../../common/processor_event';
export interface CorrelationsOptions {
environment: string;
kuery: string;
serviceName: string | undefined;
transactionType: string | undefined;
transactionName: string | undefined;
start: number;
end: number;
}
import { SearchStrategyClientParams } from '../../../../common/search_strategies/types';
export function getCorrelationsFilters({
environment,
@ -34,7 +25,7 @@ export function getCorrelationsFilters({
transactionName,
start,
end,
}: CorrelationsOptions) {
}: SearchStrategyClientParams) {
const correlationsFilters: ESFilter[] = [
{ term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } },
...rangeQuery(start, end),

View file

@ -101,10 +101,7 @@ export const fetchFailedTransactionsCorrelationPValues = async (
esClient,
params,
histogramRangeSteps,
[
{ fieldName: EVENT_OUTCOME, fieldValue: EventOutcome.failure },
{ fieldName, fieldValue: bucket.key },
]
[{ fieldName, fieldValue: bucket.key }]
);
result.push({

View file

@ -14,13 +14,19 @@ import { createApmServerRouteRepository } from './create_apm_server_route_reposi
import { environmentRt, kueryRt, rangeRt } from './default_api_types';
const latencyOverallDistributionRoute = createApmServerRoute({
endpoint: 'GET /internal/apm/latency/overall_distribution',
endpoint: 'POST /internal/apm/latency/overall_distribution',
params: t.type({
query: t.intersection([
body: t.intersection([
t.partial({
serviceName: t.string,
transactionName: t.string,
transactionType: t.string,
termFilters: t.array(
t.type({
fieldName: t.string,
fieldValue: t.union([t.string, toNumberRt]),
})
),
}),
environmentRt,
kueryRt,
@ -43,7 +49,8 @@ const latencyOverallDistributionRoute = createApmServerRoute({
start,
end,
percentileThreshold,
} = resources.params.query;
termFilters,
} = resources.params.body;
return getOverallLatencyDistribution({
environment,
@ -54,6 +61,7 @@ const latencyOverallDistributionRoute = createApmServerRoute({
start,
end,
percentileThreshold,
termFilters,
setup,
});
},

View file

@ -6788,7 +6788,6 @@
"xpack.apm.transactionActionMenu.viewSampleDocumentLinkLabel": "サンプルドキュメントを表示",
"xpack.apm.transactionBreakdown.chartTitle": "スパンタイプ別時間",
"xpack.apm.transactionDetails.clearSelectionAriaLabel": "選択した項目をクリア",
"xpack.apm.transactionDetails.distribution.errorTitle": "分布の取得中にエラーが発生しました",
"xpack.apm.transactionDetails.distribution.panelTitle": "レイテンシ分布",
"xpack.apm.transactionDetails.emptySelectionText": "クリックおよびドラッグして範囲を選択",
"xpack.apm.transactionDetails.noTraceParentButtonTooltip": "トレースの親が見つかりませんでした",

View file

@ -6844,7 +6844,6 @@
"xpack.apm.transactionActionMenu.viewSampleDocumentLinkLabel": "查看样例文档",
"xpack.apm.transactionBreakdown.chartTitle": "跨度类型花费的时间",
"xpack.apm.transactionDetails.clearSelectionAriaLabel": "清除所选内容",
"xpack.apm.transactionDetails.distribution.errorTitle": "获取分布时出错",
"xpack.apm.transactionDetails.distribution.panelTitle": "延迟分布",
"xpack.apm.transactionDetails.emptySelectionText": "单击并拖动以选择范围",
"xpack.apm.transactionDetails.errorCount": "{errorCount, number} 个 {errorCount, plural, other {错误}}",

View file

@ -12,17 +12,17 @@ import { registry } from '../../common/registry';
export default function ApiTest({ getService }: FtrProviderContext) {
const apmApiClient = getService('apmApiClient');
const endpoint = 'GET /internal/apm/latency/overall_distribution';
const endpoint = 'POST /internal/apm/latency/overall_distribution';
// This matches the parameters used for the other tab's search strategy approach in `../correlations/*`.
const getOptions = () => ({
params: {
query: {
body: {
environment: 'ENVIRONMENT_ALL',
start: '2020',
end: '2021',
kuery: '',
percentileThreshold: '95',
percentileThreshold: 95,
},
},
});