mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[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:
parent
36d128c3e7
commit
975beb125a
16 changed files with 430 additions and 158 deletions
|
@ -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();
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
|
@ -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 },
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
},
|
||||
|
|
|
@ -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": "トレースの親が見つかりませんでした",
|
||||
|
|
|
@ -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 {错误}}",
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue