Skip to content

Commit

Permalink
[APM][ECO] Logs callout when logs.level is not available (elastic#189644
Browse files Browse the repository at this point in the history
)

closes elastic#189731
- Adding dismiss button to APM section
- Adding logs callout


https://github.com/user-attachments/assets/902409de-7d9b-47bf-b57d-3cdb199301f0

- N/A popover
<img width="1222" alt="Screenshot 2024-07-31 at 15 03 43"
src="https://github.com/user-attachments/assets/4a718a04-d649-4992-a1b9-53e31d832449">

<img width="1680" alt="Screenshot 2024-08-06 at 15 01 43"
src="https://github.com/user-attachments/assets/f07f1d47-cd70-43ee-a0f8-01577d492406">
<img width="1629" alt="Screenshot 2024-08-06 at 15 01 52"
src="https://github.com/user-attachments/assets/4b0d973f-3945-40f5-8a51-97e17242c7a1">
  • Loading branch information
cauemarcondes authored Aug 7, 2024
1 parent b265604 commit 0559de6
Show file tree
Hide file tree
Showing 16 changed files with 332 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ export interface EntityServiceListItem {
environments: string[];
serviceName: string;
agentName: AgentName;
hasLogMetrics: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,19 @@ import {
EuiTitle,
EuiButtonEmpty,
useEuiTheme,
EuiButtonIcon,
} from '@elastic/eui';
import { apmLight } from '@kbn/shared-svg';
import { FormattedMessage } from '@kbn/i18n-react';
import { useKibana } from '../../../../context/kibana_context/use_kibana';
import { ApmPluginStartDeps, ApmServices } from '../../../../plugin';
import { AddApmData } from '../../../shared/add_data_buttons/buttons';

export function AddAPMCallOut() {
interface Props {
onClose: () => void;
}

export function AddAPMCallOut({ onClose }: Props) {
const { euiTheme } = useEuiTheme();
const { services } = useKibana<ApmPluginStartDeps & ApmServices>();

Expand All @@ -35,41 +40,56 @@ export function AddAPMCallOut() {

return (
<EuiPanel color="subdued" hasShadow={false}>
<EuiFlexGroup alignItems="center" gutterSize="s" justifyContent="flexStart">
<EuiFlexItem grow={0}>
<EuiImage
css={{
background: euiTheme.colors.emptyShade,
}}
width="160"
height="100"
size="m"
src={apmLight}
alt="apm-logo"
/>
</EuiFlexItem>
<EuiFlexItem grow={4}>
<EuiTitle size="xs">
<h1>
<FormattedMessage
id="xpack.apm.addAPMCallOut.title"
defaultMessage="Detect and resolve issues faster with deep visibility into your application"
<EuiFlexGroup gutterSize="s">
<EuiFlexItem>
<EuiFlexGroup alignItems="center" gutterSize="s" justifyContent="flexStart">
<EuiFlexItem grow={0}>
<EuiImage
css={{
background: euiTheme.colors.emptyShade,
}}
width="160"
height="100"
size="m"
src={apmLight}
alt="apm-logo"
/>
</h1>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={4}>
<EuiTitle size="xs">
<h1>
<FormattedMessage
id="xpack.apm.addAPMCallOut.title"
defaultMessage="Detect and resolve issues faster with deep visibility into your application"
/>
</h1>
</EuiTitle>

<EuiSpacer size="m" />
<EuiSpacer size="m" />

<EuiText size="s">
<p>
<FormattedMessage
id="xpack.apm.addAPMCallOut.description"
defaultMessage="Understanding your application performance, relationships and dependencies by
instrumenting with APM."
<EuiText size="s">
<p>
<FormattedMessage
id="xpack.apm.addAPMCallOut.description"
defaultMessage="Understanding your application performance, relationships and dependencies by
instrumenting with APM."
/>
</p>
</EuiText>
<EuiSpacer size="s" />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiButtonIcon
data-test-subj="apmAddAPMCallOutButton"
iconType="cross"
onClick={onClose}
/>
</p>
</EuiText>
<EuiSpacer size="s" />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup alignItems="center" gutterSize="s" justifyContent="flexEnd">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,29 @@
* 2.0.
*/

import {
EuiCallOut,
EuiFlexGroup,
EuiFlexGroupProps,
EuiFlexItem,
EuiLink,
EuiLoadingSpinner,
EuiSpacer,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import React from 'react';
import { EuiFlexGroupProps, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { AnnotationsContextProvider } from '../../../../context/annotations/annotations_context';
import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
import { ChartPointerEventContextProvider } from '../../../../context/chart_pointer_event/chart_pointer_event_context';
import { useBreakpoints } from '../../../../hooks/use_breakpoints';
import { useApmParams } from '../../../../hooks/use_apm_params';
import { useBreakpoints } from '../../../../hooks/use_breakpoints';
import { useTimeRange } from '../../../../hooks/use_time_range';
import { AddAPMCallOut } from './add_apm_callout';
import { LogRateChart } from '../charts/log_rate_chart';
import { LogErrorRateChart } from '../charts/log_error_rate_chart';
import { LogRateChart } from '../charts/log_rate_chart';
import { AddAPMCallOut } from './add_apm_callout';
import { useLocalStorage } from '../../../../hooks/use_local_storage';
import { isPending, useFetcher } from '../../../../hooks/use_fetcher';
/**
* The height a chart should be if it's next to a table with 5 rows and a title.
* Add the height of the pagination row.
Expand All @@ -32,18 +44,39 @@ const chartHeight = 400;

export function LogsServiceOverview() {
const { serviceName } = useApmServiceContext();
const [isLogsApmCalloutEnabled, setIsLogsApmCalloutEnabled] = useLocalStorage(
'apm.isLogsApmCalloutEnabled',
true
);

const {
query: { environment, rangeFrom, rangeTo },
} = useApmParams('/logs-services/{serviceName}/overview');

const { start, end } = useTimeRange({ rangeFrom, rangeTo });

const { data, status } = useFetcher(
(callAPI) => {
return callAPI('GET /internal/apm/entities/services/{serviceName}/summary', {
params: { path: { serviceName }, query: { end, environment, start } },
});
},
[end, environment, serviceName, start]
);

const { isLarge } = useBreakpoints();
const isSingleColumn = isLarge;

const rowDirection: EuiFlexGroupProps['direction'] = isSingleColumn ? 'column' : 'row';

if (isPending(status)) {
return (
<div style={{ textAlign: 'center' }}>
<EuiLoadingSpinner size="xl" />
</div>
);
}

return (
<AnnotationsContextProvider
serviceName={serviceName}
Expand All @@ -52,8 +85,60 @@ export function LogsServiceOverview() {
end={end}
>
<ChartPointerEventContextProvider>
<AddAPMCallOut />
<EuiSpacer size="l" />
{isLogsApmCalloutEnabled ? (
<>
<AddAPMCallOut
onClose={() => {
setIsLogsApmCalloutEnabled(false);
}}
/>
<EuiSpacer size="l" />
</>
) : null}
{data?.entity?.hasLogMetrics === false ? (
<>
<EuiCallOut
title={i18n.translate(
'xpack.apm.logsServiceOverview.euiCallOut.noLogMetricsHaveLabel',
{
defaultMessage: 'No log metrics have been detected against this service',
}
)}
color="warning"
iconType="warning"
>
<FormattedMessage
id="xpack.apm.logsServiceOverview.pleaseEnsureYouAreCallOutLabel"
defaultMessage="Please ensure you are surfacing {logLevelLink} in your logs to display log metrics. {learnMoreLink}"
values={{
logLevelLink: (
<EuiLink
data-test-subj="apmNotAvailableLogsMetricsLink"
href="https://www.elastic.co/guide/en/ecs/current/ecs-log.html#field-log-level"
target="_blank"
>
{i18n.translate('xpack.apm.logsServiceOverview.logLevelLink', {
defaultMessage: 'log.level',
})}
</EuiLink>
),
learnMoreLink: (
<EuiLink
data-test-subj="apmNotAvailableLogsMetricsLink"
href="https://ela.st/service-logs-level"
target="_blank"
>
{i18n.translate('xpack.apm.logsServiceOverview.learnMoreLink', {
defaultMessage: 'Learn more',
})}
</EuiLink>
),
}}
/>
</EuiCallOut>
<EuiSpacer size="l" />
</>
) : null}

<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ import { EnvironmentBadge } from '../../../../shared/environment_badge';
import { ServiceLink } from '../../../../shared/links/apm/service_link';
import { ListMetric } from '../../../../shared/list_metric';
import { ITableColumn } from '../../../../shared/managed_table';
import { NotAvailableApmMetrics } from '../../../../shared/not_available_apm_metrics';
import { NotAvailableApmMetrics } from '../../../../shared/not_available_popover/not_available_apm_metrics';
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 { isApmSignal, isLogsSignal } from '../../../../../utils/get_signal_type';
import { ColumnHeader } from './column_header';
import { APIReturnType } from '../../../../../services/rest/create_call_apm_api';
import { NotAvailableLogsMetrics } from '../../../../shared/not_available_popover/not_available_log_metrics';

type ServicesDetailedStatisticsAPIResponse =
APIReturnType<'POST /internal/apm/entities/services/detailed_statistics'>;
Expand Down Expand Up @@ -205,9 +206,12 @@ export function getServiceColumns({
sortable: true,
dataType: 'number',
align: RIGHT_ALIGNMENT,
render: (_, { metrics, serviceName }) => {
const { currentPeriodColor } = getTimeSeriesColor(ChartType.LOG_RATE);
render: (_, { metrics, serviceName, signalTypes, hasLogMetrics }) => {
if (isLogsSignal(signalTypes) && !hasLogMetrics) {
return <NotAvailableLogsMetrics />;
}

const { currentPeriodColor } = getTimeSeriesColor(ChartType.LOG_RATE);
return (
<ListMetric
isLoading={timeseriesDataLoading}
Expand Down Expand Up @@ -254,7 +258,11 @@ export function getServiceColumns({
sortable: true,
dataType: 'number',
align: RIGHT_ALIGNMENT,
render: (_, { metrics, serviceName }) => {
render: (_, { metrics, serviceName, signalTypes, hasLogMetrics }) => {
if (isLogsSignal(signalTypes) && !hasLogMetrics) {
return <NotAvailableLogsMetrics />;
}

const { currentPeriodColor } = getTimeSeriesColor(ChartType.LOG_ERROR_RATE);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { ItemsBadge } from '../item_badge';
import { NotAvailableEnvironment } from '../not_available_environment';
import { NotAvailableEnvironment } from '../not_available_popover/not_available_environment';

interface Props {
environments: string[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@

import React from 'react';
import { i18n } from '@kbn/i18n';
import { PopoverBadge } from './popover_badge';
import { useKibana } from '../../context/kibana_context/use_kibana';
import { ApmPluginStartDeps, ApmServices } from '../../plugin';
import { AddApmData } from './add_data_buttons/buttons';
import { PopoverBadge } from '../popover_badge';
import { useKibana } from '../../../context/kibana_context/use_kibana';
import { ApmPluginStartDeps, ApmServices } from '../../../plugin';
import { AddApmData } from '../add_data_buttons/buttons';

export function NotAvailableApmMetrics() {
const { services } = useKibana<ApmPluginStartDeps & ApmServices>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import React from 'react';
import { EuiCode, EuiLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { PopoverBadge } from './popover_badge';
import { PopoverBadge } from '../popover_badge';

export const NotAvailableEnvironment = () => {
export function NotAvailableEnvironment() {
return (
<PopoverBadge
title={i18n.translate('xpack.apm.servicesTable.notAvailableEnv.title', {
Expand Down Expand Up @@ -39,4 +39,4 @@ export const NotAvailableEnvironment = () => {
}
/>
);
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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 { EuiLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import React from 'react';
import { PopoverBadge } from '../popover_badge';

export function NotAvailableLogsMetrics() {
return (
<PopoverBadge
title={i18n.translate('xpack.apm.servicesTable.notAvailableLogsMetrics.title', {
defaultMessage: 'Want to see more?',
})}
content={
<FormattedMessage
id="xpack.apm.servicesTable.notAvailableLogsMetrics.content"
defaultMessage="In order to see log metrics against this service, please declare {logLevelLink} in your logs."
values={{
logLevelLink: (
<EuiLink
data-test-subj="apmNotAvailableLogsMetricsLink"
href="https://www.elastic.co/guide/en/ecs/current/ecs-log.html#field-log-level"
target="_blank"
>
{i18n.translate(
'xpack.apm.servicesTable.notAvailableLogsMetrics.content.logLevelLink',
{ defaultMessage: 'log.level' }
)}
</EuiLink>
),
}}
/>
}
footer={
<EuiLink
data-test-subj="apmNotAvailableLogsMetricsLink"
href="https://ela.st/service-logs-level"
target="_blank"
>
{i18n.translate('xpack.apm.servicesTable.notAvailableLogsMetrics.footer.learnMore', {
defaultMessage: 'Learn more',
})}
</EuiLink>
}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ export function isApmSignal(signalTypes: SignalTypes[]) {
return signalTypes.includes(SignalTypes.METRICS) || signalTypes.includes(SignalTypes.TRACES);
}
export function isLogsSignal(signalTypes: SignalTypes[]) {
return signalTypes.includes(SignalTypes.LOGS) && !isApmSignal(signalTypes);
return signalTypes.includes(SignalTypes.LOGS);
}
Loading

0 comments on commit 0559de6

Please sign in to comment.