Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('getColumns', () => {
const docId = 'span-456';
const errorId = 'error-789';

const mockErrorItem: ErrorsByTraceId['traceErrors'][0] = {
const mockErrorItem = {
error: {
id: errorId,
exception: {
Expand All @@ -36,9 +36,9 @@ describe('getColumns', () => {
grouping_key: 'group-123',
},
timestamp: { us: 1234567890 },
};
} as unknown as ErrorsByTraceId['traceErrors'][0];

const mockErrorItemWithLog: ErrorsByTraceId['traceErrors'][0] = {
const mockErrorItemWithLog = {
error: {
id: errorId,
log: {
Expand All @@ -47,16 +47,16 @@ describe('getColumns', () => {
culprit: 'test-culprit',
},
timestamp: { us: 1234567890 },
};
} as unknown as ErrorsByTraceId['traceErrors'][0];

const mockUnprocessedOtelErrorItem: ErrorsByTraceId['traceErrors'][0] = {
const mockUnprocessedOtelErrorItem = {
error: {
id: errorId,
exception: {
type: 'Error',
},
},
};
} as unknown as ErrorsByTraceId['traceErrors'][0];

beforeEach(() => {
jest.clearAllMocks();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@ export interface ErrorData {
}

export interface Error {
id: string;
parent?: { id?: string };
trace?: { id?: string };
span?: { id?: string };
transaction?: { id?: string };
service: { name: string };
eventName?: string;
error: ErrorData;
timestamp?: TimestampUs | undefined;
timestamp: TimestampUs;
}

export interface ErrorsByTraceId {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ export interface WaterfallSpan {
child?: { id: string[] };
}

/**
* @deprecated Use Error instead
*/
export interface WaterfallError {
id: string;
timestamp: TimestampUs;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ describe('getErrorMarks', () => {
docType: 'error',
offset: 10,
skew: 5,
id: 1,
doc: { error: { id: 1 }, service: { name: 'opbeans-java' } },
color: 'red',
} as unknown,
{
docType: 'error',
offset: 50,
skew: 0,
id: 2,
doc: { error: { id: 2 }, service: { name: 'opbeans-node' } },
color: 'blue',
} as unknown,
Expand All @@ -40,6 +42,7 @@ describe('getErrorMarks', () => {
id: 1,
error: { error: { id: 1 }, service: { name: 'opbeans-java' } },
serviceColor: 'red',
withLink: true,
},
{
type: 'errorMark',
Expand All @@ -48,6 +51,7 @@ describe('getErrorMarks', () => {
id: 2,
error: { error: { id: 2 }, service: { name: 'opbeans-node' } },
serviceColor: 'blue',
withLink: true,
},
]);
});
Expand All @@ -58,13 +62,15 @@ describe('getErrorMarks', () => {
docType: 'error',
offset: 10,
skew: 5,
id: 1,
doc: { error: { id: 1 }, service: { name: 'opbeans-java' } },
color: '',
} as unknown,
{
docType: 'error',
offset: 50,
skew: 0,
id: 2,
doc: { error: { id: 2 }, service: { name: 'opbeans-node' } },
color: '',
} as unknown,
Expand All @@ -77,6 +83,7 @@ describe('getErrorMarks', () => {
id: 1,
error: { error: { id: 1 }, service: { name: 'opbeans-java' } },
serviceColor: '',
withLink: true,
},
{
type: 'errorMark',
Expand All @@ -85,6 +92,7 @@ describe('getErrorMarks', () => {
id: 2,
error: { error: { id: 2 }, service: { name: 'opbeans-node' } },
serviceColor: '',
withLink: true,
},
]);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@
*/

import { isEmpty } from 'lodash';
import type { Error } from '@kbn/apm-types';
import type { IWaterfallError } from '../waterfall/waterfall_helpers/waterfall_helpers';
import type { Mark } from '.';
import type { WaterfallError } from '../../../../../../../common/waterfall/typings';

export interface ErrorMark extends Mark {
type: 'errorMark';
error: WaterfallError;
error: Error;
serviceColor: string;
withLink?: boolean;
onClick?: () => void;
}

/**
* @deprecated Remove it once we remove the apm waterfall
*/
export const getErrorMarks = (errorItems: IWaterfallError[]): ErrorMark[] => {
if (isEmpty(errorItems)) {
return [];
Expand All @@ -25,8 +30,9 @@ export const getErrorMarks = (errorItems: IWaterfallError[]): ErrorMark[] => {
type: 'errorMark',
offset: Math.max(error.offset + error.skew, 0),
verticalLine: false,
id: error.doc.error.id,
id: error.id,
error: error.doc,
serviceColor: error.color,
withLink: true,
}));
};
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ import {
import type {
WaterfallSpan,
WaterfallTransaction,
WaterfallError,
} from '../../../../../../../../common/waterfall/typings';
import { WaterfallLegendType } from '../../../../../../../../common/waterfall/legend';
import type { Error } from '@kbn/apm-types';

describe('waterfall_helpers', () => {
const hits = [
Expand Down Expand Up @@ -140,7 +140,7 @@ describe('waterfall_helpers', () => {
name: 'ruby',
version: '2',
},
} as WaterfallError,
} as Error,
];

describe('getWaterfall', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import { euiPaletteColorBlind } from '@elastic/eui';
import { ProcessorEvent } from '@kbn/observability-plugin/common';
import type { Dictionary } from 'lodash';
import { first, flatten, groupBy, isEmpty, sortBy, uniq } from 'lodash';
import type { Error } from '@kbn/apm-types';
import type { IWaterfallLegend } from '../../../../../../../../common/waterfall/legend';
import { WaterfallLegendType } from '../../../../../../../../common/waterfall/legend';
import { isOpenTelemetryAgentName } from '../../../../../../../../common/agent_name';
import type { CriticalPathSegment } from '../../../../../../../../common/critical_path/types';
import type {
WaterfallError,
WaterfallSpan,
WaterfallTransaction,
} from '../../../../../../../../common/waterfall/typings';
Expand Down Expand Up @@ -78,7 +78,7 @@ interface IWaterfallItemBase<TDocument, TDoctype> {
}

export type IWaterfallError = Omit<
IWaterfallItemBase<WaterfallError, 'error'>,
IWaterfallItemBase<Error, 'error'>,
'duration' | 'legendValues' | 'spanLinksCount'
>;

Expand Down Expand Up @@ -158,7 +158,7 @@ export function getSpanItem(span: WaterfallSpan, linkedChildrenCount: number = 0
}

function getErrorItem(
error: WaterfallError,
error: Error,
items: IWaterfallItem[],
entryWaterfallTransaction?: IWaterfallTransaction
): IWaterfallError {
Expand All @@ -170,7 +170,7 @@ function getErrorItem(
const errorItem: IWaterfallError = {
docType: 'error',
doc: error,
id: error.error.id,
id: error.error.id ?? error.id,
parent,
parentId: parent?.id,
offset: error.timestamp.us - entryTimestamp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@
* 2.0.
*/

import { EuiPopover, EuiText, useEuiTheme } from '@elastic/eui';
import React, { useState } from 'react';
import { EuiButtonEmpty, EuiPopover, EuiText, useEuiTheme } from '@elastic/eui';
import styled from '@emotion/styled';
import { useAnyOfApmParams } from '../../../../../hooks/use_apm_params';
import { TRACE_ID, TRANSACTION_ID } from '../../../../../../common/es_fields/apm';
import type { TypeOf } from '@kbn/typed-react-router-config';
import React, { useState } from 'react';
import { asDuration } from '../../../../../../common/utils/formatters';
import type { ErrorMark } from '../../../../app/transaction_details/waterfall_with_summary/waterfall_container/marks/get_error_marks';
import type { ApmRoutes } from '../../../../routing/apm_route_config';
import { ErrorDetailLink } from '../../../links/apm/error_detail_link';
import { Legend, Shape } from '../legend';

interface Props {
mark: ErrorMark;
query?: TypeOf<ApmRoutes, '/services/{serviceName}/errors/{groupId}'>['query'];
}

const Popover = styled.div`
Expand Down Expand Up @@ -49,19 +50,9 @@ function truncateMessage(errorMessage?: string) {
}
}

export function ErrorMarker({ mark }: Props) {
export function ErrorMarker({ mark, query }: Props) {
const { euiTheme } = useEuiTheme();
const [isPopoverOpen, showPopover] = useState(false);
const { query } = useAnyOfApmParams(
'/services/{serviceName}/overview',
'/services/{serviceName}/errors',
'/services/{serviceName}/transactions/view',
'/mobile-services/{serviceName}/overview',
'/mobile-services/{serviceName}/transactions/view',
'/mobile-services/{serviceName}/errors-and-crashes',
'/traces/explorer/waterfall',
'/dependencies/operation'
);

const togglePopover = () => showPopover(!isPopoverOpen);

Expand All @@ -76,18 +67,8 @@ export function ErrorMarker({ mark }: Props) {
);

const { error } = mark;
const serviceGroup = 'serviceGroup' in query ? query.serviceGroup : '';

const queryParam = {
...query,
serviceGroup,
kuery: [
...(error.trace?.id ? [`${TRACE_ID} : "${error.trace?.id}"`] : []),
...(error.transaction?.id ? [`${TRANSACTION_ID} : "${error.transaction?.id}"`] : []),
].join(' and '),
};

const errorMessage = error.error.log?.message || error.error.exception?.[0]?.message;
const errorMessage = error.error.log?.message || error.error.exception?.message;
const truncatedErrorMessage = truncateMessage(errorMessage);

return (
Expand All @@ -110,15 +91,29 @@ export function ErrorMarker({ mark }: Props) {
indicator={<span />}
/>
<EuiText size="s">
<ErrorLink
data-test-subj="errorLink"
serviceName={error.service.name}
errorGroupId={error.error.grouping_key}
query={queryParam}
title={errorMessage}
>
{truncatedErrorMessage}
</ErrorLink>
{mark.onClick === undefined && error.error.grouping_key && query ? (
<ErrorLink
data-test-subj="errorLink"
serviceName={error.service.name}
errorGroupId={error.error.grouping_key}
query={query}
title={errorMessage}
>
{truncatedErrorMessage}
</ErrorLink>
) : mark.onClick ? (
<EuiButtonEmpty
data-test-subj="apmErrorMarkerButton"
onClick={() => {
togglePopover();
mark.onClick?.();
}}
>
{truncatedErrorMessage}
</EuiButtonEmpty>
) : (
truncatedErrorMessage
)}
</EuiText>
</Popover>
</EuiPopover>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { MemoryRouter } from 'react-router-dom';
import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context';
import { expectTextsInDocument, renderWithTheme } from '../../../../../utils/test_helpers';
import type { ErrorMark } from '../../../../app/transaction_details/waterfall_with_summary/waterfall_container/marks/get_error_marks';
import { ErrorMarker } from './error_marker';
import { ErrorMarkerWithLink } from './error_marker_with_link';

function Wrapper({ children }: { children?: ReactNode }) {
return (
Expand All @@ -26,7 +26,7 @@ function Wrapper({ children }: { children?: ReactNode }) {
);
}

describe('ErrorMarker', () => {
describe('ErrorMarkerWithLink', () => {
const mark = {
id: 'agent',
offset: 10000,
Expand All @@ -48,7 +48,7 @@ describe('ErrorMarker', () => {
} as unknown as ErrorMark;

function openPopover(errorMark: ErrorMark) {
const component = renderWithTheme(<ErrorMarker mark={errorMark} />, {
const component = renderWithTheme(<ErrorMarkerWithLink mark={errorMark} />, {
wrapper: Wrapper,
});
act(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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 from 'react';
import { ErrorMarker } from './error_marker';
import { TRACE_ID, TRANSACTION_ID } from '../../../../../../common/es_fields/apm';
import { useAnyOfApmParams } from '../../../../../hooks/use_apm_params';
import type { ErrorMark } from '../../../../app/transaction_details/waterfall_with_summary/waterfall_container/marks/get_error_marks';

export function ErrorMarkerWithLink({ mark }: { mark: ErrorMark }) {
const { query } = useAnyOfApmParams(
'/services/{serviceName}/overview',
'/services/{serviceName}/errors',
'/services/{serviceName}/transactions/view',
'/mobile-services/{serviceName}/overview',
'/mobile-services/{serviceName}/transactions/view',
'/mobile-services/{serviceName}/errors-and-crashes',
'/traces/explorer/waterfall',
'/dependencies/operation'
);

const serviceGroup = 'serviceGroup' in query ? query.serviceGroup : '';

const queryParam = {
...query,
serviceGroup,
kuery: [
...(mark.error.trace?.id ? [`${TRACE_ID} : "${mark.error.trace?.id}"`] : []),
...(mark.error.transaction?.id
? [`${TRANSACTION_ID} : "${mark.error.transaction?.id}"`]
: []),
].join(' and '),
};

return <ErrorMarker mark={mark} query={queryParam} />;
}
Loading