diff --git a/x-pack/solutions/observability/plugins/apm/ftr_e2e/cypress/e2e/dependency_operation/dependency_operation.cy.ts b/x-pack/solutions/observability/plugins/apm/ftr_e2e/cypress/e2e/dependency_operation/dependency_operation.cy.ts index 530d96f00153a..6a72a50357e31 100644 --- a/x-pack/solutions/observability/plugins/apm/ftr_e2e/cypress/e2e/dependency_operation/dependency_operation.cy.ts +++ b/x-pack/solutions/observability/plugins/apm/ftr_e2e/cypress/e2e/dependency_operation/dependency_operation.cy.ts @@ -47,4 +47,11 @@ describe('Dependency operation', () => { cy.getByTestSubj('apmActionMenuButtonInvestigateButton').click(); cy.getByTestSubj('apmActionMenuInvestigateButtonPopup'); }); + + it('opens Span link flyout', () => { + cy.visitKibana(dependencyOperationHref); + cy.contains('Span links').click(); + cy.contains('Span details'); + cy.contains('Span B'); + }); }); diff --git a/x-pack/solutions/observability/plugins/apm/ftr_e2e/cypress/fixtures/synthtrace/opbeans.ts b/x-pack/solutions/observability/plugins/apm/ftr_e2e/cypress/fixtures/synthtrace/opbeans.ts index b936f2eb8eb7d..0192744d62443 100644 --- a/x-pack/solutions/observability/plugins/apm/ftr_e2e/cypress/fixtures/synthtrace/opbeans.ts +++ b/x-pack/solutions/observability/plugins/apm/ftr_e2e/cypress/fixtures/synthtrace/opbeans.ts @@ -4,10 +4,28 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import type { ApmFields } from '@kbn/apm-synthtrace-client'; +import { apm, timerange, generateLongId, generateShortId } from '@kbn/apm-synthtrace-client'; +import { shuffle, compact } from 'lodash'; + +function generateExternalSpanLinks() { + return Array(2) + .fill(0) + .map(() => ({ span: { id: generateShortId() }, trace: { id: generateLongId() } })); +} + +function getSpanLinksFromEvents(events: ApmFields[]) { + return compact( + events.map((event) => { + const spanId = event['span.id']; + return spanId ? { span: { id: spanId }, trace: { id: event['trace.id']! } } : undefined; + }) + ); +} export function opbeans({ from, to }: { from: number; to: number }) { const range = timerange(from, to); + const producerTimestamps = range.ratePerMinute(1); const opbeansJava = apm .service({ @@ -32,6 +50,25 @@ export function opbeans({ from, to }: { from: number; to: number }) { userAgent: apm.getChromeUserAgentDefaults(), }); + const opbeansJavaInternalOnlyEvents = producerTimestamps.generator((timestamp) => + opbeansJava + .transaction({ transactionName: 'Transaction A' }) + .timestamp(timestamp) + .duration(1000) + .success() + .children( + opbeansJava + .span({ spanName: 'Span A', spanType: 'messaging', spanSubtype: 'kafka' }) + .timestamp(timestamp) + .duration(100) + .success() + ) + ); + + const serializedOpbeansJavaInternalOnlyEvents = Array.from(opbeansJavaInternalOnlyEvents).flatMap( + (event) => event.serialize() + ); + return range .interval('1s') .rate(1) @@ -57,7 +94,17 @@ export function opbeans({ from, to }: { from: number; to: number }) { .timestamp(timestamp) .duration(50) .failure() - .destination('postgresql') + .destination('postgresql'), + opbeansJava + .span({ spanName: 'Span B', spanType: 'messaging', spanSubtype: 'kafka' }) + .defaults({ + 'span.links': shuffle([ + ...generateExternalSpanLinks(), + ...getSpanLinksFromEvents(serializedOpbeansJavaInternalOnlyEvents), + ]), + }) + .timestamp(timestamp) + .duration(900) ), opbeansNode .transaction({ transactionName: 'GET /api/product/:id' }) diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/span_links/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/span_links/index.tsx index 6a19db9380958..eb6215150c97a 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/shared/span_links/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/span_links/index.tsx @@ -33,7 +33,8 @@ export function SpanLinks({ spanLinksCount, traceId, spanId, processorEvent }: P } = useAnyOfApmParams( '/services/{serviceName}/transactions/view', '/mobile-services/{serviceName}/transactions/view', - '/traces/explorer/waterfall' + '/traces/explorer/waterfall', + '/dependencies/operation' ); const { start, end } = useTimeRange({ rangeFrom, rangeTo }); diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/span_links/span_links_table.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/span_links/span_links_table.tsx index f1c84b7a4987a..e48c359af4eb6 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/shared/span_links/span_links_table.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/span_links/span_links_table.tsx @@ -40,7 +40,8 @@ export function SpanLinksTable({ items }: Props) { } = useAnyOfApmParams( '/services/{serviceName}/transactions/view', '/mobile-services/{serviceName}/transactions/view', - '/traces/explorer/waterfall' + '/traces/explorer/waterfall', + '/dependencies/operation' ); const [idActionMenuOpen, setIdActionMenuOpen] = useState(); const {