diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/entities/charts/log_error_rate_chart.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/entities/charts/log_error_rate_chart.tsx index 56daf2d42d797..db893e717e099 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/entities/charts/log_error_rate_chart.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/entities/charts/log_error_rate_chart.tsx @@ -7,6 +7,8 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiPanel, EuiTitle, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { css } from '@emotion/react'; import { useApmParams } from '../../../../hooks/use_apm_params'; import { useFetcher } from '../../../../hooks/use_fetcher'; import { useTimeRange } from '../../../../hooks/use_time_range'; @@ -14,6 +16,12 @@ import { APIReturnType } from '../../../../services/rest/create_call_apm_api'; import { getTimeSeriesColor, ChartType } from '../../../shared/charts/helper/get_timeseries_color'; import { TimeseriesChartWithContext } from '../../../shared/charts/timeseries_chart_with_context'; import { yLabelAsPercent } from '../../../../../common/utils/formatters'; +import { TooltipContent } from '../../service_inventory/multi_signal_inventory/table/tooltip_content'; +import { Popover } from '../../service_inventory/multi_signal_inventory/table/popover'; +import { + ChartMetricType, + getMetricsFormula, +} from '../../../shared/charts/helper/get_metrics_formulas'; type LogErrorRateReturnType = APIReturnType<'GET /internal/apm/entities/services/{serviceName}/logs_error_rate_timeseries'>; @@ -77,6 +85,35 @@ export function LogErrorRateChart({ height }: { height: number }) { + + + + {i18n.translate( + 'xpack.apm.multiSignal.servicesTable.logErrorRate.tooltip.serviceNameLabel', + { + defaultMessage: 'service.name', + } + )} + + ), + }} + /> + } + /> + + ; @@ -77,6 +85,35 @@ export function LogRateChart({ height }: { height: number }) { + + + + {i18n.translate( + 'xpack.apm.multiSignal.servicesTable.logRatePerMinute.tooltip.serviceNameLabel', + { + defaultMessage: 'service.name', + } + )} + + ), + }} + /> + } + /> + + ( + +
+ {label} +
+ + {toolTip && ( + + + + )} +
+)); diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/get_service_columns.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/get_service_columns.tsx index a5661e36141ac..0fe647b2c5127 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/get_service_columns.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/get_service_columns.tsx @@ -9,6 +9,8 @@ import { EuiFlexGroup, EuiFlexItem, RIGHT_ALIGNMENT } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { TypeOf } from '@kbn/typed-react-router-config'; import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { css } from '@emotion/react'; import { asDecimalOrInteger, asMillisecondDuration, @@ -22,6 +24,10 @@ import { getTimeSeriesColor, ChartType, } from '../../../../shared/charts/helper/get_timeseries_color'; +import { + getMetricsFormula, + ChartMetricType, +} from '../../../../shared/charts/helper/get_metrics_formulas'; import { EnvironmentBadge } from '../../../../shared/environment_badge'; import { ServiceLink } from '../../../../shared/links/apm/service_link'; import { ListMetric } from '../../../../shared/list_metric'; @@ -31,6 +37,7 @@ import { TruncateWithTooltip } from '../../../../shared/truncate_with_tooltip'; import { ServiceInventoryFieldName } from './multi_signal_services_table'; import { EntityServiceListItem, SignalTypes } from '../../../../../../common/entities/types'; import { isApmSignal } from '../../../../../utils/get_signal_type'; +import { ColumnHeader } from './column_header'; import { APIReturnType } from '../../../../../services/rest/create_call_apm_api'; type ServicesDetailedStatisticsAPIResponse = @@ -167,9 +174,36 @@ export function getServiceColumns({ }, { field: ServiceInventoryFieldName.LogRatePerMinute, - name: i18n.translate('xpack.apm.multiSignal.servicesTable.logRatePerMinute', { - defaultMessage: 'Log rate (per min.)', - }), + name: ( + + {i18n.translate( + 'xpack.apm.multiSignal.servicesTable.logRatePerMinute.tooltip.serviceNameLabel', + { + defaultMessage: 'service.name', + } + )} + + ), + }} + /> + } + /> + ), sortable: true, dataType: 'number', align: RIGHT_ALIGNMENT, @@ -189,9 +223,36 @@ export function getServiceColumns({ }, { field: ServiceInventoryFieldName.LogErrorRate, - name: i18n.translate('xpack.apm.multiSignal.servicesTable.logErrorRate', { - defaultMessage: 'Log error rate', - }), + name: ( + + {i18n.translate( + 'xpack.apm.multiSignal.servicesTable.logErrorRate.tooltip.serviceNameLabel', + { + defaultMessage: 'service.name', + } + )} + + ), + }} + /> + } + /> + ), sortable: true, dataType: 'number', align: RIGHT_ALIGNMENT, diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/popover.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/popover.tsx new file mode 100644 index 0000000000000..3d06ee109906c --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/popover.tsx @@ -0,0 +1,56 @@ +/* + * 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 { EuiPopover, EuiIcon, type IconColor, type IconSize } from '@elastic/eui'; +import { css } from '@emotion/react'; +import React from 'react'; +import { useBoolean } from '@kbn/react-hooks'; + +export function Popover({ + children, + iconColor, + iconSize, + ...props +}: { + children: React.ReactNode; + iconColor?: IconColor; + iconSize?: IconSize; + 'data-test-subj'?: string; +}) { + const [isPopoverOpen, { off: closePopover, toggle: togglePopover }] = useBoolean(false); + + return ( + { + e.stopPropagation(); + togglePopover(); + }} + css={css` + display: flex; + `} + data-test-subj={props['data-test-subj']} + > + + + } + isOpen={isPopoverOpen} + offset={10} + closePopover={closePopover} + repositionOnScroll + anchorPosition="upCenter" + panelStyle={{ maxWidth: 350 }} + > + {children} + + ); +} diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/tooltip_content.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/tooltip_content.tsx new file mode 100644 index 0000000000000..8551c56ef6961 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/tooltip_content.tsx @@ -0,0 +1,46 @@ +/* + * 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 React, { HTMLAttributes, ReactElement } from 'react'; +import { EuiText } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { FormattedMessage } from '@kbn/i18n-react'; + +export interface TooltipContentProps extends Pick, 'style'> { + description: ReactElement | string; + formula?: string; +} + +export const TooltipContent = React.memo(({ description, formula, style }: TooltipContentProps) => { + const onClick = (e: React.MouseEvent) => { + e.stopPropagation(); + }; + + return ( + +

{description}

+ {formula && ( +

+ + + +
+ + {formula} + +

+ )} +
+ ); +}); diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/charts/helper/get_metrics_formulas.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/charts/helper/get_metrics_formulas.tsx new file mode 100644 index 0000000000000..5e969d250e1dd --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/charts/helper/get_metrics_formulas.tsx @@ -0,0 +1,20 @@ +/* + * 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. + */ + +export enum ChartMetricType { + LOG_ERROR_RATE, + LOG_RATE, +} + +const metricsFormulasMap: Record = { + [ChartMetricType.LOG_RATE]: `count(kql='log.level: *') / [PERIOD_IN_MINUTES]`, + [ChartMetricType.LOG_ERROR_RATE]: `count(kql='log.level: "error" OR log.level: "ERROR"') / count(kql='log.level: *')`, +}; + +export function getMetricsFormula(chartMetricType: ChartMetricType) { + return metricsFormulasMap[chartMetricType]; +} diff --git a/x-pack/plugins/observability_solution/apm/tsconfig.json b/x-pack/plugins/observability_solution/apm/tsconfig.json index fc7fccbded5dd..c081fce2e783b 100644 --- a/x-pack/plugins/observability_solution/apm/tsconfig.json +++ b/x-pack/plugins/observability_solution/apm/tsconfig.json @@ -122,7 +122,8 @@ "@kbn/react-kibana-context-theme", "@kbn/test-jest-helpers", "@kbn/security-plugin-types-common", - "@kbn/entityManager-plugin" + "@kbn/entityManager-plugin", + "@kbn/react-hooks" ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_error_rate_timeseries/get_logs_error_rate_timeseries.ts b/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_error_rate_timeseries/get_logs_error_rate_timeseries.ts index fdc71e5b84685..1d80171e7d0b0 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_error_rate_timeseries/get_logs_error_rate_timeseries.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_error_rate_timeseries/get_logs_error_rate_timeseries.ts @@ -60,6 +60,8 @@ export function createGetLogErrorRateTimeseries() { }: LogsErrorRateTimeseries): Promise => { const intervalString = getBucketSizeFromTimeRangeAndBucketCount(timeFrom, timeTo, 50); + // Note: Please keep the formula in `metricsFormulasMap` up to date with the query! + const esResponse = await esClient.search({ index: 'logs-*-*', size: 0, diff --git a/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_rate_timeseries/get_logs_rate_timeseries.ts b/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_rate_timeseries/get_logs_rate_timeseries.ts index c0e9704931578..49b2d9cb578fb 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_rate_timeseries/get_logs_rate_timeseries.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_rate_timeseries/get_logs_rate_timeseries.ts @@ -51,6 +51,8 @@ export function createGetLogsRateTimeseries() { }: LogsRateTimeseries): Promise => { const intervalString = getBucketSizeFromTimeRangeAndBucketCount(timeFrom, timeTo, 50); + // Note: Please keep the formula in `metricsFormulasMap` up to date with the query! + const esResponse = await esClient.search({ index: 'logs-*-*', size: 0,