Skip to content
Merged
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 @@ -7,5 +7,5 @@

export interface Server {
address?: string;
port?: string;
port?: number;
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import { ErrorTabKey, getTabs } from './error_tabs';
import { ErrorUiActionsContextMenu } from './error_ui_actions_context_menu';
import { SampleSummary } from './sample_summary';
import { ErrorSampleContextualInsight } from './error_sample_contextual_insight';
import { buildUrl } from '../../../../utils/build_url';

const TransactionLinkName = styled.div`
margin-left: ${({ theme }) => theme.euiTheme.size.s};
Expand Down Expand Up @@ -154,11 +155,22 @@ export function ErrorSampleDetails({

const tabs = getTabs(error);
const currentTab = getCurrentTab(tabs, detailTab) as ErrorTab;
const urlFromError = error.error.page?.url || error.url?.full;
const urlFromTransaction = transaction?.transaction?.page?.url || transaction?.url?.full;
const errorOrTransactionUrl = error?.url ? error : transaction;
const errorOrTransactionHttp = error?.http ? error : transaction;
const errorOrTransactionUserAgent = error?.user_agent
? error.user_agent
: transaction?.user_agent;

const errorUrl = error.error.page?.url || error.url?.full;
const method = error.http?.request?.method;
const status = error.http?.response?.status_code;
const userAgent = error?.user_agent;
// To get the error data needed for the summary we use the transaction fallback in case
// the error data is not available.
// In case of OTel the error data is not available in the error response and we need to use
// the associated root span data (which is called "transaction" here because of the APM data model).
const errorUrl = urlFromError || urlFromTransaction || buildUrl(errorOrTransactionUrl);
const method = errorOrTransactionHttp?.http?.request?.method;
const status = errorOrTransactionHttp?.http?.response?.status_code;
const userAgent = errorOrTransactionUserAgent;
const environment = error.service.environment;
const serviceVersion = error.service.version;
const isUnhandled = error.error.exception?.[0]?.handled === false;
Expand Down Expand Up @@ -205,7 +217,7 @@ export function ErrorSampleDetails({
<EuiFlexItem>
<EuiIcon type="apmTrace" />
</EuiFlexItem>
<EuiFlexItem style={{ whiteSpace: 'nowrap' }}>
<EuiFlexItem css={{ whiteSpace: 'nowrap' }}>
{i18n.translate('xpack.apm.errorSampleDetails.viewOccurrencesInTraceExplorer', {
defaultMessage: 'Explore traces with this error',
})}
Expand All @@ -223,7 +235,7 @@ export function ErrorSampleDetails({
<EuiFlexItem>
<EuiIcon type="discoverApp" />
</EuiFlexItem>
<EuiFlexItem style={{ whiteSpace: 'nowrap' }}>
<EuiFlexItem css={{ whiteSpace: 'nowrap' }}>
{i18n.translate(
'xpack.apm.errorSampleDetails.viewOccurrencesInDiscoverButtonLabel',
{
Expand All @@ -247,7 +259,7 @@ export function ErrorSampleDetails({
<Summary
items={[
<TimestampTooltip time={errorData ? error.timestamp.us / 1000 : 0} />,
errorUrl && method ? (
errorUrl ? (
<HttpInfoSummaryItem url={errorUrl} method={method} status={status} />
) : null,
userAgent?.name ? <UserAgentSummaryItem {...userAgent} /> : null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
* 2.0.
*/

import { buildUrl } from './build_url';
import type { Transaction } from '../../typings/es_schemas/ui/transaction';
import { type ItemType, buildUrl } from './build_url';

describe('buildUrl', () => {
it('should return a full URL when all fields are provided', () => {
Expand All @@ -20,7 +19,7 @@ describe('buildUrl', () => {
port: 443,
},
};
const result = buildUrl(item as unknown as Transaction);
const result = buildUrl(item);
expect(result).toBe('ftp://example.com:443/some/path');
});

Expand All @@ -34,7 +33,7 @@ describe('buildUrl', () => {
address: 'example.org',
},
};
const result = buildUrl(item as Transaction);
const result = buildUrl(item);
expect(result).toBe('http://example.org/another/path');
});

Expand All @@ -48,7 +47,7 @@ describe('buildUrl', () => {
port: 8443,
},
};
const result = buildUrl(item as unknown as Transaction);
const result = buildUrl(item);
expect(result).toBe('https://example.net:8443/');
});

Expand All @@ -62,7 +61,7 @@ describe('buildUrl', () => {
port: 8080,
},
};
const result = buildUrl(item as unknown as Transaction);
const result = buildUrl(item);
expect(result).toBeUndefined();
});

Expand All @@ -76,7 +75,7 @@ describe('buildUrl', () => {
port: 8080,
},
};
const result = buildUrl(item as unknown as Transaction);
const result = buildUrl(item);
expect(result).toBeUndefined();
});

Expand All @@ -90,17 +89,17 @@ describe('buildUrl', () => {
},
server: {
address: 'example.com',
port: 'invalid-port',
port: 'invalid', // Invalid port
},
};

const result = buildUrl(item as unknown as Transaction);
const result = buildUrl(item as unknown as ItemType);

expect(result).toBeUndefined();
expect(consoleErrorSpy).toHaveBeenCalledWith(
'Failed to build URL',
expect.objectContaining({
message: 'Invalid base URL: https://example.com:invalid-port',
message: 'Invalid base URL: https://example.com:invalid',
})
);

Expand All @@ -109,7 +108,7 @@ describe('buildUrl', () => {

it('should handle an empty object gracefully', () => {
const item = {};
const result = buildUrl(item as Transaction);
const result = buildUrl(item);
expect(result).toBeUndefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { Transaction } from '../../typings/es_schemas/ui/transaction';
import type { Span } from '../../typings/es_schemas/ui/span';
import type { APMError } from '../../typings/es_schemas/ui/apm_error';
export interface ItemType {
url?: {
scheme?: string;
path?: string;
};
server?: {
address?: string;
port?: number;
};
}

export const buildUrl = (item: Transaction | Span | APMError) => {
export const buildUrl = (item?: ItemType) => {
// URL fields from Otel
const urlScheme = item?.url?.scheme;
const urlPath = item?.url?.path;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ export async function getTransaction({

return {
...event,
server: {
...event.server,
port: event.server?.port ? Number(event.server?.port) : undefined,
},
transaction: {
...event.transaction,
marks: source?.transaction?.marks,
Expand Down