diff --git a/x-pack/platform/packages/shared/kbn-apm-types/src/es_schemas/raw/fields/server.ts b/x-pack/platform/packages/shared/kbn-apm-types/src/es_schemas/raw/fields/server.ts
index 49e3520f19fce..c170426e4f736 100644
--- a/x-pack/platform/packages/shared/kbn-apm-types/src/es_schemas/raw/fields/server.ts
+++ b/x-pack/platform/packages/shared/kbn-apm-types/src/es_schemas/raw/fields/server.ts
@@ -7,5 +7,5 @@
export interface Server {
address?: string;
- port?: string;
+ port?: number;
}
diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/error_group_details/error_sampler/error_sample_detail.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/error_group_details/error_sampler/error_sample_detail.tsx
index 4d147e78237cb..462f33b83a4a3 100644
--- a/x-pack/solutions/observability/plugins/apm/public/components/app/error_group_details/error_sampler/error_sample_detail.tsx
+++ b/x-pack/solutions/observability/plugins/apm/public/components/app/error_group_details/error_sampler/error_sample_detail.tsx
@@ -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};
@@ -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;
@@ -205,7 +217,7 @@ export function ErrorSampleDetails({
-
+
{i18n.translate('xpack.apm.errorSampleDetails.viewOccurrencesInTraceExplorer', {
defaultMessage: 'Explore traces with this error',
})}
@@ -223,7 +235,7 @@ export function ErrorSampleDetails({
-
+
{i18n.translate(
'xpack.apm.errorSampleDetails.viewOccurrencesInDiscoverButtonLabel',
{
@@ -247,7 +259,7 @@ export function ErrorSampleDetails({
,
- errorUrl && method ? (
+ errorUrl ? (
) : null,
userAgent?.name ? : null,
diff --git a/x-pack/solutions/observability/plugins/apm/public/utils/build_url.test.ts b/x-pack/solutions/observability/plugins/apm/public/utils/build_url.test.ts
index 603fb536418fc..5b7e8ae27a176 100644
--- a/x-pack/solutions/observability/plugins/apm/public/utils/build_url.test.ts
+++ b/x-pack/solutions/observability/plugins/apm/public/utils/build_url.test.ts
@@ -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', () => {
@@ -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');
});
@@ -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');
});
@@ -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/');
});
@@ -62,7 +61,7 @@ describe('buildUrl', () => {
port: 8080,
},
};
- const result = buildUrl(item as unknown as Transaction);
+ const result = buildUrl(item);
expect(result).toBeUndefined();
});
@@ -76,7 +75,7 @@ describe('buildUrl', () => {
port: 8080,
},
};
- const result = buildUrl(item as unknown as Transaction);
+ const result = buildUrl(item);
expect(result).toBeUndefined();
});
@@ -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',
})
);
@@ -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();
});
});
diff --git a/x-pack/solutions/observability/plugins/apm/public/utils/build_url.ts b/x-pack/solutions/observability/plugins/apm/public/utils/build_url.ts
index 30f74fb58ac27..e5a16508a0d23 100644
--- a/x-pack/solutions/observability/plugins/apm/public/utils/build_url.ts
+++ b/x-pack/solutions/observability/plugins/apm/public/utils/build_url.ts
@@ -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;
diff --git a/x-pack/solutions/observability/plugins/apm/server/routes/transactions/get_transaction/index.ts b/x-pack/solutions/observability/plugins/apm/server/routes/transactions/get_transaction/index.ts
index 858ed68556923..3926ca41c1367 100644
--- a/x-pack/solutions/observability/plugins/apm/server/routes/transactions/get_transaction/index.ts
+++ b/x-pack/solutions/observability/plugins/apm/server/routes/transactions/get_transaction/index.ts
@@ -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,