diff --git a/src/platform/packages/shared/kbn-discover-utils/index.ts b/src/platform/packages/shared/kbn-discover-utils/index.ts index 5e84904fc5a38..742cc1b2c06db 100644 --- a/src/platform/packages/shared/kbn-discover-utils/index.ts +++ b/src/platform/packages/shared/kbn-discover-utils/index.ts @@ -41,8 +41,8 @@ export { formatHit, getDocId, getLogDocumentOverview, - getTransactionDocumentOverview, - getSpanDocumentOverview, + getTraceDocumentOverview, + getFlattenedTraceDocumentOverview, getIgnoredReason, getMessageFieldWithFallbacks, getShouldShowFieldHandler, diff --git a/src/platform/packages/shared/kbn-discover-utils/src/types.ts b/src/platform/packages/shared/kbn-discover-utils/src/types.ts index 7fb37ff81609e..da11d49e7fbb8 100644 --- a/src/platform/packages/shared/kbn-discover-utils/src/types.ts +++ b/src/platform/packages/shared/kbn-discover-utils/src/types.ts @@ -113,19 +113,12 @@ export interface LogCloudFields { 'cloud.instance.id'?: string; } -export interface TransactionDocumentOverview +export interface TraceDocumentOverview extends TraceFields, - ServiceFields, - TransactionFields, - UserAgentFields {} - -export interface SpanDocumentOverview - extends TraceFields, - ServiceFields, - SpanFields, - UserAgentFields { - 'transaction.id'?: string; - 'transaction.name'?: string; + Partial, + Partial, + Partial, + Partial { duration?: number; kind?: string; 'resource.attributes.telemetry.sdk.language'?: string; diff --git a/src/platform/packages/shared/kbn-discover-utils/src/utils/get_span_document_overview.ts b/src/platform/packages/shared/kbn-discover-utils/src/utils/get_trace_document_overview.ts similarity index 75% rename from src/platform/packages/shared/kbn-discover-utils/src/utils/get_span_document_overview.ts rename to src/platform/packages/shared/kbn-discover-utils/src/utils/get_trace_document_overview.ts index d4788a847958c..58bdf6dd2884e 100644 --- a/src/platform/packages/shared/kbn-discover-utils/src/utils/get_span_document_overview.ts +++ b/src/platform/packages/shared/kbn-discover-utils/src/utils/get_trace_document_overview.ts @@ -9,12 +9,12 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; -import type { DataTableRecord, SpanDocumentOverview } from '../types'; +import type { DataTableRecord, TraceDocumentOverview } from '../types'; import { fieldConstants } from '..'; import { getFormattedFields } from './get_formatted_fields'; import { getFlattenedFields } from './get_flattened_fields'; -const fields: Array = [ +const fields: Array = [ fieldConstants.TIMESTAMP_FIELD, fieldConstants.PARENT_ID_FIELD, fieldConstants.HTTP_RESPONSE_STATUS_CODE_FIELD, @@ -24,6 +24,10 @@ const fields: Array = [ fieldConstants.AGENT_NAME_FIELD, fieldConstants.TRANSACTION_ID_FIELD, fieldConstants.TRANSACTION_NAME_FIELD, + fieldConstants.TRANSACTION_DURATION_FIELD, + fieldConstants.TRANSACTION_TYPE_FIELD, + fieldConstants.USER_AGENT_NAME_FIELD, + fieldConstants.USER_AGENT_VERSION_FIELD, fieldConstants.SPAN_NAME_FIELD, fieldConstants.SPAN_ID_FIELD, fieldConstants.SPAN_ACTION_FIELD, @@ -43,13 +47,13 @@ const fields: Array = [ fieldConstants.OTEL_LINKS_TRACE_ID, ]; -export function getSpanDocumentOverview( +export function getTraceDocumentOverview( doc: DataTableRecord, { dataView, fieldFormats }: { dataView: DataView; fieldFormats: FieldFormatsStart } -): SpanDocumentOverview { - return getFormattedFields(doc, fields, { dataView, fieldFormats }); +): TraceDocumentOverview { + return getFormattedFields(doc, fields, { dataView, fieldFormats }); } -export function getFlattenedSpanDocumentOverview(doc: DataTableRecord): SpanDocumentOverview { - return getFlattenedFields(doc, fields); +export function getFlattenedTraceDocumentOverview(doc: DataTableRecord): TraceDocumentOverview { + return getFlattenedFields(doc, fields); } diff --git a/src/platform/packages/shared/kbn-discover-utils/src/utils/get_transaction_document_overview.ts b/src/platform/packages/shared/kbn-discover-utils/src/utils/get_transaction_document_overview.ts deleted file mode 100644 index a9ab144f2ae7d..0000000000000 --- a/src/platform/packages/shared/kbn-discover-utils/src/utils/get_transaction_document_overview.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import type { DataView } from '@kbn/data-views-plugin/common'; -import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; -import { fieldConstants } from '..'; -import type { DataTableRecord, TransactionDocumentOverview } from '../types'; -import { getFormattedFields } from './get_formatted_fields'; -import { getFlattenedFields } from './get_flattened_fields'; - -const fields: Array = [ - fieldConstants.TIMESTAMP_FIELD, - fieldConstants.PARENT_ID_FIELD, - fieldConstants.HTTP_RESPONSE_STATUS_CODE_FIELD, - fieldConstants.TRACE_ID_FIELD, - fieldConstants.SERVICE_NAME_FIELD, - fieldConstants.SERVICE_ENVIRONMENT_FIELD, - fieldConstants.AGENT_NAME_FIELD, - fieldConstants.TRANSACTION_ID_FIELD, - fieldConstants.TRANSACTION_TYPE_FIELD, - fieldConstants.TRANSACTION_NAME_FIELD, - fieldConstants.TRANSACTION_DURATION_FIELD, - fieldConstants.USER_AGENT_NAME_FIELD, - fieldConstants.USER_AGENT_VERSION_FIELD, - fieldConstants.PROCESSOR_EVENT_FIELD, -]; - -export function getTransactionDocumentOverview( - doc: DataTableRecord, - { dataView, fieldFormats }: { dataView: DataView; fieldFormats: FieldFormatsStart } -): TransactionDocumentOverview { - return getFormattedFields(doc, fields, { dataView, fieldFormats }); -} - -export function getFlattenedTransactionDocumentOverview( - doc: DataTableRecord -): TransactionDocumentOverview { - return getFlattenedFields(doc, fields); -} diff --git a/src/platform/packages/shared/kbn-discover-utils/src/utils/index.ts b/src/platform/packages/shared/kbn-discover-utils/src/utils/index.ts index 4b8c30543f4d9..45b33d262ac33 100644 --- a/src/platform/packages/shared/kbn-discover-utils/src/utils/index.ts +++ b/src/platform/packages/shared/kbn-discover-utils/src/utils/index.ts @@ -15,8 +15,7 @@ export * from './format_value'; export * from './get_doc_id'; export * from './get_ignored_reason'; export * from './get_log_document_overview'; -export * from './get_transaction_document_overview'; -export * from './get_span_document_overview'; +export * from './get_trace_document_overview'; export * from './get_message_field_with_fallbacks'; export * from './get_should_show_field_handler'; export * from './get_stack_trace_fields'; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/inspector/profiles_inspector_view/document_profiles_section/document_profile_table.test.tsx b/src/platform/plugins/shared/discover/public/context_awareness/inspector/profiles_inspector_view/document_profiles_section/document_profile_table.test.tsx index 9516f649881b6..0d36b72ca9210 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/inspector/profiles_inspector_view/document_profiles_section/document_profile_table.test.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/inspector/profiles_inspector_view/document_profiles_section/document_profile_table.test.tsx @@ -62,12 +62,12 @@ describe('', () => { getDataTableRecordWithContextMock({ id: 'record2', raw: generateEsHit({ _id: 'some other record' }), - context: getDocumentContextMock({ type: DocumentType.Span }), + context: getDocumentContextMock({ type: DocumentType.Trace }), }), getDataTableRecordWithContextMock({ id: 'record3', raw: generateEsHit({ _id: 'one specific record' }), - context: getDocumentContextMock({ type: DocumentType.Transaction }), + context: getDocumentContextMock({ type: DocumentType.Trace }), }), ]; @@ -87,8 +87,7 @@ describe('', () => { // Then expect(screen.getByText('log')).toBeVisible(); - expect(screen.getByText('span')).toBeVisible(); - expect(screen.getByText('transaction')).toBeVisible(); + expect(screen.getAllByText('trace')).toHaveLength(2); }); describe('when the inspect action is clicked', () => { diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/observability_profile_providers.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/observability_profile_providers.ts index cf51f868efa19..8e4e2de43b289 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/observability_profile_providers.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/observability_profile_providers.ts @@ -9,15 +9,13 @@ import type { ProfileProviderServices } from '../profile_provider_services'; import { createObservabilityLogDocumentProfileProvider } from './log_document_profile'; -import { createObservabilityTracesSpanDocumentProfileProvider } from './traces_document_profile/span_document_profile'; -import { createObservabilityTracesTransactionDocumentProfileProvider } from './traces_document_profile/transaction_document_profile'; +import { createObservabilityTracesDocumentProfileProvider } from './traces_document_profile/document_profile'; export const createObservabilityDocumentProfileProviders = ( providerServices: ProfileProviderServices ) => { return [ createObservabilityLogDocumentProfileProvider(providerServices), - createObservabilityTracesSpanDocumentProfileProvider(providerServices), - createObservabilityTracesTransactionDocumentProfileProvider(providerServices), + createObservabilityTracesDocumentProfileProvider(providerServices), ]; }; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/span_document_profile/accessors/doc_viewer.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/document_profile/accessors/doc_viewer.tsx similarity index 79% rename from src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/span_document_profile/accessors/doc_viewer.tsx rename to src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/document_profile/accessors/doc_viewer.tsx index b17f8bfec1a88..5ba0330520d96 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/span_document_profile/accessors/doc_viewer.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/document_profile/accessors/doc_viewer.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { UnifiedDocViewerObservabilityTracesSpanOverview } from '@kbn/unified-doc-viewer-plugin/public'; +import { UnifiedDocViewerObservabilityTracesOverview } from '@kbn/unified-doc-viewer-plugin/public'; import type { DocViewsRegistry } from '@kbn/unified-doc-viewer'; import type { TraceIndexes } from '@kbn/discover-utils/src/data_types/traces/types'; import type { DocumentProfileProvider } from '../../../../../profiles'; @@ -20,19 +20,19 @@ export const createGetDocViewer = (prev: (params: DocViewerExtensionParams) => DocViewerExtension) => (params: DocViewerExtensionParams) => { const prevDocViewer = prev(params); - const tabTitle = i18n.translate('discover.docViews.observability.traces.spanOverview.title', { - defaultMessage: 'Span overview', + const tabTitle = i18n.translate('discover.docViews.observability.traces.overview.title', { + defaultMessage: 'Overview', }); return { ...prevDocViewer, docViewsRegistry: (registry: DocViewsRegistry) => { registry.add({ - id: 'doc_view_obs_traces_span_overview', + id: 'doc_view_obs_traces_overview', title: tabTitle, order: 0, - component: (props) => { - return ; - }, + component: (props) => ( + + ), }); return prevDocViewer.docViewsRegistry(registry); diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/span_document_profile/accessors/index.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/document_profile/accessors/index.ts similarity index 100% rename from src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/span_document_profile/accessors/index.ts rename to src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/document_profile/accessors/index.ts diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/transaction_document_profile/accessors/index.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/document_profile/index.ts similarity index 85% rename from src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/transaction_document_profile/accessors/index.ts rename to src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/document_profile/index.ts index 07c68375c440c..3ec1c1a7431f3 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/transaction_document_profile/accessors/index.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/document_profile/index.ts @@ -7,4 +7,4 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export { createGetDocViewer } from './doc_viewer'; +export { createObservabilityTracesDocumentProfileProvider } from './profile'; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/transaction_document_profile/profile.test.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/document_profile/profile.test.ts similarity index 58% rename from src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/transaction_document_profile/profile.test.ts rename to src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/document_profile/profile.test.ts index 5295e3e8f68de..139cfd2201b6f 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/transaction_document_profile/profile.test.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/document_profile/profile.test.ts @@ -10,13 +10,13 @@ import { buildDataTableRecord } from '@kbn/discover-utils'; import type { DataSourceContext, RootContext } from '../../../../profiles'; import { DataSourceCategory, DocumentType, SolutionType } from '../../../../profiles'; -import { createObservabilityTracesTransactionDocumentProfileProvider } from './profile'; +import { createContextAwarenessMocks } from '../../../../__mocks__'; +import { createObservabilityTracesDocumentProfileProvider } from './profile'; import type { ContextWithProfileId } from '../../../../profile_service'; import { OBSERVABILITY_ROOT_PROFILE_ID } from '../../consts'; -import { createContextAwarenessMocks } from '../../../../__mocks__'; import type { ProfileProviderServices } from '../../../profile_provider_services'; -describe('transactionDocumentProfileProvider', () => { +describe('tracesDocumentProfileProvider', () => { const getRootContext = ({ profileId, solutionType, @@ -31,13 +31,13 @@ describe('transactionDocumentProfileProvider', () => { }; const DATA_SOURCE_CONTEXT: ContextWithProfileId = { - profileId: 'traces-transaction-document-profile', + profileId: 'traces-document-profile', category: DataSourceCategory.Traces, }; const RESOLUTION_MATCH = { isMatch: true, context: { - type: DocumentType.Transaction, + type: DocumentType.Trace, }, }; const RESOLUTION_MISMATCH = { @@ -50,47 +50,59 @@ describe('transactionDocumentProfileProvider', () => { describe('when root profile is observability', () => { const profileId = OBSERVABILITY_ROOT_PROFILE_ID; - const transactionDocumentProfileProvider = - createObservabilityTracesTransactionDocumentProfileProvider(mockServices); - it('matches records with the correct data stream type and the correct processor event', () => { + const spanDocumentProfileProvider = + createObservabilityTracesDocumentProfileProvider(mockServices); + + it('matches records with at least the correct source and a trace id', () => { expect( - transactionDocumentProfileProvider.resolve({ + spanDocumentProfileProvider.resolve({ rootContext: getRootContext({ profileId }), dataSourceContext: DATA_SOURCE_CONTEXT, - record: buildMockRecord('index', { - 'data_stream.type': ['traces'], - 'processor.event': ['transaction'], + record: buildTraceMockRecord('index', { + 'trace.id': ['c0ffee'], }), }) ).toEqual(RESOLUTION_MATCH); }); - it('does not match records with neither characteristic', () => { + it('does not match records with no trace id', () => { expect( - transactionDocumentProfileProvider.resolve({ + spanDocumentProfileProvider.resolve({ rootContext: getRootContext({ profileId }), dataSourceContext: DATA_SOURCE_CONTEXT, - record: buildMockRecord('another-index'), + record: buildTraceMockRecord('another-index'), }) ).toEqual(RESOLUTION_MISMATCH); }); + + it('matches records with the correct trace id and any OTEL `kind` field (unprocessed spans)', () => { + expect( + spanDocumentProfileProvider.resolve({ + rootContext: getRootContext({ profileId }), + dataSourceContext: DATA_SOURCE_CONTEXT, + record: buildTraceMockRecord('index', { + 'trace.id': ['c0ffee'], + kind: 'Internal', + }), + }) + ).toEqual(RESOLUTION_MATCH); + }); }); - describe('when solutionType is NOT observability', () => { - const profileId = OBSERVABILITY_ROOT_PROFILE_ID; - const solutionType = SolutionType.Default; - const transactionDocumentProfileProvider = - createObservabilityTracesTransactionDocumentProfileProvider(mockServices); + describe('when root profile is NOT observability', () => { + const profileId = 'another-profile'; + const solutionType = SolutionType.Security; + const spanDocumentProfileProvider = + createObservabilityTracesDocumentProfileProvider(mockServices); - it('does not match records with the correct data stream type and the correct processor event', () => { + it('does not match records with the correct data source and a trace id', () => { expect( - transactionDocumentProfileProvider.resolve({ + spanDocumentProfileProvider.resolve({ rootContext: getRootContext({ profileId, solutionType }), dataSourceContext: DATA_SOURCE_CONTEXT, - record: buildMockRecord('index', { - 'data_stream.type': ['traces'], - 'processor.event': ['transaction'], + record: buildTraceMockRecord('index', { + 'trace.id': ['c0ffee'], }), }) ).toEqual(RESOLUTION_MISMATCH); @@ -98,7 +110,7 @@ describe('transactionDocumentProfileProvider', () => { }); }); -const buildMockRecord = (index: string, fields: Record = {}) => +const buildTraceMockRecord = (index: string, fields: Record = {}) => buildDataTableRecord({ _id: '', _index: index, diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/span_document_profile/profile.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/document_profile/profile.ts similarity index 53% rename from src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/span_document_profile/profile.ts rename to src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/document_profile/profile.ts index 4536edc3449bc..c6d2623bd6433 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/span_document_profile/profile.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/document_profile/profile.ts @@ -7,17 +7,17 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { DataTableRecord } from '@kbn/discover-utils'; -import { DATASTREAM_TYPE_FIELD, getFieldValue, PROCESSOR_EVENT_FIELD } from '@kbn/discover-utils'; +import { type DataTableRecord, TRACE_ID_FIELD, getFieldValue } from '@kbn/discover-utils'; +import type { ContextWithProfileId } from '../../../../profile_service'; import { TRACES_PRODUCT_FEATURE_ID } from '../../../../../../common/constants'; -import type { DocumentProfileProvider } from '../../../../profiles'; -import { DocumentType, SolutionType } from '../../../../profiles'; +import type { DataSourceContext, DocumentProfileProvider } from '../../../../profiles'; +import { DataSourceCategory, DocumentType, SolutionType } from '../../../../profiles'; import type { ProfileProviderServices } from '../../../profile_provider_services'; import { createGetDocViewer } from './accessors'; -const OBSERVABILITY_TRACES_SPAN_DOCUMENT_PROFILE_ID = 'observability-traces-span-document-profile'; +const OBSERVABILITY_TRACES_SPAN_DOCUMENT_PROFILE_ID = 'observability-traces-document-profile'; -export const createObservabilityTracesSpanDocumentProfileProvider = ({ +export const createObservabilityTracesDocumentProfileProvider = ({ tracesContextService, apmErrorsContextService, logsContextService, @@ -33,40 +33,29 @@ export const createObservabilityTracesSpanDocumentProfileProvider = ({ logs: logsContextService.getAllLogsIndexPattern(), }), }, - resolve: ({ record, rootContext }) => { + resolve: ({ record, rootContext, dataSourceContext }) => { const isObservabilitySolutionView = rootContext.solutionType === SolutionType.Observability; - if (!isObservabilitySolutionView) { - return { isMatch: false }; + if (isObservabilitySolutionView && isTraceDocument(record, dataSourceContext)) { + return { + isMatch: true, + context: { + type: DocumentType.Trace, + }, + }; } - const isSpanRecord = getIsSpanRecord({ - record, - }); - - if (!isSpanRecord) { - return { isMatch: false }; - } - - return { - isMatch: true, - context: { - type: DocumentType.Span, - }, - }; + return { isMatch: false }; }, }); -const getIsSpanRecord = ({ record }: { record: DataTableRecord }) => { - return isSpanDocument(record); -}; - -const isSpanDocument = (record: DataTableRecord) => { - const dataStreamType = getFieldValue(record, DATASTREAM_TYPE_FIELD); - const processorEvent = getFieldValue(record, PROCESSOR_EVENT_FIELD); - - const isApmSpan = processorEvent === 'span'; - const isOtelSpan = processorEvent == null; +function isTraceDocument( + record: DataTableRecord, + dataSourceContext: ContextWithProfileId +): boolean { + const traceId = getFieldValue(record, TRACE_ID_FIELD); - return dataStreamType === 'traces' && (isApmSpan || isOtelSpan); -}; + // TODO: Relying on this data source check is a hack, this should be refactored to use + // _index field once ES|QL queries return default metadata. + return dataSourceContext.category === DataSourceCategory.Traces && !!traceId; +} diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/span_document_profile/index.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/span_document_profile/index.ts deleted file mode 100644 index 18a0109782d24..0000000000000 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/span_document_profile/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -export { createObservabilityTracesSpanDocumentProfileProvider } from './profile'; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/span_document_profile/profile.test.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/span_document_profile/profile.test.ts deleted file mode 100644 index 275023f5a7c10..0000000000000 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/span_document_profile/profile.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { buildDataTableRecord } from '@kbn/discover-utils'; -import type { DataSourceContext, RootContext } from '../../../../profiles'; -import { DataSourceCategory, DocumentType, SolutionType } from '../../../../profiles'; -import { createContextAwarenessMocks } from '../../../../__mocks__'; -import { createObservabilityTracesSpanDocumentProfileProvider } from './profile'; -import type { ContextWithProfileId } from '../../../../profile_service'; -import { OBSERVABILITY_ROOT_PROFILE_ID } from '../../consts'; -import type { ProfileProviderServices } from '../../../profile_provider_services'; - -describe('spanDocumentProfileProvider', () => { - const getRootContext = ({ - profileId, - solutionType, - }: { - profileId: string; - solutionType?: SolutionType; - }): ContextWithProfileId => { - return { - profileId, - solutionType: solutionType ?? SolutionType.Observability, - }; - }; - - const DATA_SOURCE_CONTEXT: ContextWithProfileId = { - profileId: 'traces-span-document-profile', - category: DataSourceCategory.Traces, - }; - const RESOLUTION_MATCH = { - isMatch: true, - context: { - type: DocumentType.Span, - }, - }; - const RESOLUTION_MISMATCH = { - isMatch: false, - }; - - const mockServices: ProfileProviderServices = { - ...createContextAwarenessMocks().profileProviderServices, - }; - - describe('when root profile is observability', () => { - const profileId = OBSERVABILITY_ROOT_PROFILE_ID; - - const spanDocumentProfileProvider = - createObservabilityTracesSpanDocumentProfileProvider(mockServices); - - it('matches records with the correct data stream type and the correct processor event', () => { - expect( - spanDocumentProfileProvider.resolve({ - rootContext: getRootContext({ profileId }), - dataSourceContext: DATA_SOURCE_CONTEXT, - record: buildMockRecord('index', { - 'data_stream.type': ['traces'], - 'processor.event': ['span'], - }), - }) - ).toEqual(RESOLUTION_MATCH); - }); - - it('does not match records with neither characteristic', () => { - expect( - spanDocumentProfileProvider.resolve({ - rootContext: getRootContext({ profileId }), - dataSourceContext: DATA_SOURCE_CONTEXT, - record: buildMockRecord('another-index'), - }) - ).toEqual(RESOLUTION_MISMATCH); - }); - - it('does not match records with the correct data stream type but the incorrect processor event', () => { - expect( - spanDocumentProfileProvider.resolve({ - rootContext: getRootContext({ profileId }), - dataSourceContext: DATA_SOURCE_CONTEXT, - record: buildMockRecord('index', { - 'data_stream.type': ['traces'], - 'processor.event': ['other'], - }), - }) - ).toEqual(RESOLUTION_MISMATCH); - }); - - it('matches records with the correct data stream type and any OTEL `kind` field (unprocessed spans)', () => { - expect( - spanDocumentProfileProvider.resolve({ - rootContext: getRootContext({ profileId }), - dataSourceContext: DATA_SOURCE_CONTEXT, - record: buildMockRecord('index', { - 'data_stream.type': ['traces'], - kind: 'Internal', - }), - }) - ).toEqual(RESOLUTION_MATCH); - }); - - it('defaults to matching records with the correct data stream type but no processor event field (unprocessed spans)', () => { - expect( - spanDocumentProfileProvider.resolve({ - rootContext: getRootContext({ profileId }), - dataSourceContext: DATA_SOURCE_CONTEXT, - record: buildMockRecord('index', { - 'data_stream.type': ['traces'], - }), - }) - ).toEqual(RESOLUTION_MATCH); - }); - }); - - describe('when root profile is NOT observability', () => { - const profileId = 'another-profile'; - const solutionType = SolutionType.Security; - const spanDocumentProfileProvider = - createObservabilityTracesSpanDocumentProfileProvider(mockServices); - - it('does not match records with the correct data stream type and the correct processor event', () => { - expect( - spanDocumentProfileProvider.resolve({ - rootContext: getRootContext({ profileId, solutionType }), - dataSourceContext: DATA_SOURCE_CONTEXT, - record: buildMockRecord('index', { - 'data_stream.type': ['traces'], - 'processor.event': ['span'], - }), - }) - ).toEqual(RESOLUTION_MISMATCH); - }); - }); -}); - -const buildMockRecord = (index: string, fields: Record = {}) => - buildDataTableRecord({ - _id: '', - _index: index, - fields: { - _index: index, - ...fields, - }, - }); diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/transaction_document_profile/accessors/doc_viewer.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/transaction_document_profile/accessors/doc_viewer.tsx deleted file mode 100644 index 1ba9af4042371..0000000000000 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/transaction_document_profile/accessors/doc_viewer.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { UnifiedDocViewerObservabilityTracesTransactionOverview } from '@kbn/unified-doc-viewer-plugin/public'; -import type { DocViewsRegistry } from '@kbn/unified-doc-viewer'; -import type { TraceIndexes } from '@kbn/discover-utils/src'; -import type { DocumentProfileProvider } from '../../../../..'; -import type { DocViewerExtensionParams, DocViewerExtension } from '../../../../../types'; - -export const createGetDocViewer = - (indexes: TraceIndexes): DocumentProfileProvider['profile']['getDocViewer'] => - (prev: (params: DocViewerExtensionParams) => DocViewerExtension) => - (params: DocViewerExtensionParams) => { - const prevDocViewer = prev(params); - const tabTitle = i18n.translate( - 'discover.docViews.observability.traces.transactionOverview.title', - { - defaultMessage: 'Transaction overview', - } - ); - - return { - ...prevDocViewer, - docViewsRegistry: (registry: DocViewsRegistry) => { - registry.add({ - id: 'doc_view_obs_traces_transaction_overview', - title: tabTitle, - order: 0, - component: (props) => { - return ( - - ); - }, - }); - - return prevDocViewer.docViewsRegistry(registry); - }, - }; - }; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/transaction_document_profile/index.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/transaction_document_profile/index.ts deleted file mode 100644 index 8195c581ca6d8..0000000000000 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/transaction_document_profile/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -export { createObservabilityTracesTransactionDocumentProfileProvider } from './profile'; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/transaction_document_profile/profile.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/transaction_document_profile/profile.ts deleted file mode 100644 index 76db0d0481f26..0000000000000 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/traces_document_profile/transaction_document_profile/profile.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import type { DataTableRecord } from '@kbn/discover-utils'; -import { DATASTREAM_TYPE_FIELD, getFieldValue, PROCESSOR_EVENT_FIELD } from '@kbn/discover-utils'; -import { TRACES_PRODUCT_FEATURE_ID } from '../../../../../../common/constants'; -import type { DocumentProfileProvider } from '../../../../profiles'; -import { DocumentType, SolutionType } from '../../../../profiles'; -import { createGetDocViewer } from './accessors'; -import type { ProfileProviderServices } from '../../../profile_provider_services'; - -const OBSERVABILITY_TRACES_TRANSACTION_DOCUMENT_PROFILE_ID = - 'observability-traces-transaction-document-profile'; - -export const createObservabilityTracesTransactionDocumentProfileProvider = ({ - tracesContextService, - apmErrorsContextService, - logsContextService, -}: ProfileProviderServices): DocumentProfileProvider => ({ - profileId: OBSERVABILITY_TRACES_TRANSACTION_DOCUMENT_PROFILE_ID, - restrictedToProductFeature: TRACES_PRODUCT_FEATURE_ID, - profile: { - getDocViewer: createGetDocViewer({ - apm: { - errors: apmErrorsContextService.getErrorsIndexPattern(), - traces: tracesContextService.getAllTracesIndexPattern(), - }, - logs: logsContextService.getAllLogsIndexPattern(), - }), - }, - resolve: ({ record, rootContext }) => { - const isObservabilitySolutionView = rootContext.solutionType === SolutionType.Observability; - - if (!isObservabilitySolutionView) { - return { isMatch: false }; - } - - const isTransactionRecord = getIsTransactionRecord({ - record, - }); - - if (!isTransactionRecord) { - return { isMatch: false }; - } - - return { - isMatch: true, - context: { - type: DocumentType.Transaction, - }, - }; - }, -}); - -const getIsTransactionRecord = ({ record }: { record: DataTableRecord }) => { - return isTransactionDocument(record); -}; - -const isTransactionDocument = (record: DataTableRecord) => { - const dataStreamType = getFieldValue(record, DATASTREAM_TYPE_FIELD); - const processorEvent = getFieldValue(record, PROCESSOR_EVENT_FIELD); - return dataStreamType === 'traces' && processorEvent === 'transaction'; -}; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profiles/document_profile.ts b/src/platform/plugins/shared/discover/public/context_awareness/profiles/document_profile.ts index 8f7350d863716..488b917930bac 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profiles/document_profile.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profiles/document_profile.ts @@ -19,8 +19,7 @@ import type { DataSourceContext } from './data_source_profile'; */ export enum DocumentType { Log = 'log', - Span = 'span', - Transaction = 'transaction', + Trace = 'trace', Default = 'default', } diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/full_screen_waterfall/span_flyout/span_flyout_body.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/full_screen_waterfall/span_flyout/span_flyout_body.tsx index 670700efe3c59..4999b1bba68f7 100644 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/full_screen_waterfall/span_flyout/span_flyout_body.tsx +++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/full_screen_waterfall/span_flyout/span_flyout_body.tsx @@ -12,12 +12,10 @@ import type { DataTableRecord } from '@kbn/discover-utils'; import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import type { DocViewRenderProps } from '@kbn/unified-doc-viewer/types'; -import SpanOverview from '../../../doc_viewer_span_overview'; -import TransactionOverview from '../../../doc_viewer_transaction_overview'; +import Overview from '../../../doc_viewer_overview'; import DocViewerTable from '../../../../../doc_viewer_table'; import DocViewerSource from '../../../../../doc_viewer_source'; import { useDataSourcesContext } from '../../../hooks/use_data_sources'; -import { isSpanHit } from '../helpers/is_span'; const tabIds = { OVERVIEW: 'unifiedDocViewerTracesSpanFlyoutOverview', @@ -58,7 +56,6 @@ export interface SpanFlyoutProps { export const SpanFlyoutBody = ({ hit, loading, dataView }: SpanFlyoutProps) => { const [selectedTabId, setSelectedTabId] = useState(tabIds.OVERVIEW); - const isSpan = isSpanHit(hit); const { indexes } = useDataSourcesContext(); const onSelectedTabChanged = (id: string) => setSelectedTabId(id); @@ -84,23 +81,13 @@ export const SpanFlyoutBody = ({ hit, loading, dataView }: SpanFlyoutProps) => { {selectedTabId === tabIds.OVERVIEW && ( - {isSpan ? ( - - ) : ( - - )} + )} diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/service_name_link.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/service_name_link.tsx index 23456af8369ae..6880555b69677 100644 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/service_name_link.tsx +++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/service_name_link.tsx @@ -17,7 +17,7 @@ const SERVICE_OVERVIEW_LOCATOR_ID = 'serviceOverviewLocator'; interface ServiceNameLinkProps { serviceName: string; - agentName: string; + agentName?: string; formattedServiceName: React.ReactNode; } diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/similar_spans/index.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/similar_spans/index.tsx index 78f2becf4ff04..0960250b8dd35 100644 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/similar_spans/index.tsx +++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/similar_spans/index.tsx @@ -13,12 +13,12 @@ import { DurationDistributionChart } from '@kbn/apm-ui-shared'; import { ProcessorEvent } from '@kbn/apm-types-shared'; import { ContentFrameworkChart } from '../../../../content_framework/chart'; import { ContentFrameworkSection } from '../../../../content_framework/section'; -import type { SpanLatencyChartData } from '../../doc_viewer_span_overview/hooks/use_span_latency_chart'; +import type { LatencyChartData } from '../../doc_viewer_overview/hooks/use_latency_chart'; export interface SimilarSpansProps { spanDuration: number; latencyChart: { - data: SpanLatencyChartData | null; // TODO move this interface + data: LatencyChartData | null; loading: boolean; hasError: boolean; }; @@ -49,10 +49,10 @@ export function SimilarSpans({ esqlQuery={!latencyChart.hasError && esqlQuery ? esqlQuery : undefined} > { spanDuration: 1200, latencyChart: { data: { - spanDistributionChartData: mockSpanDistributionChartData, + distributionChartData: mockSpanDistributionChartData, percentileThresholdValue: 1000, }, loading: false, @@ -104,7 +104,7 @@ describe('SimilarSpans', () => { {...defaultProps} latencyChart={{ data: { - spanDistributionChartData: [], + distributionChartData: [], }, loading: false, hasError: true, diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/trace.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/trace.tsx index f25484d8a2566..491b430443491 100644 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/trace.tsx +++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/trace.tsx @@ -14,10 +14,8 @@ import { i18n } from '@kbn/i18n'; import type { DocViewRenderProps } from '@kbn/unified-doc-viewer/types'; import type { DataViewField } from '@kbn/data-views-plugin/common'; import { SERVICE_NAME_FIELD } from '@kbn/discover-utils'; -import { spanTraceFields } from '../doc_viewer_span_overview/resources/fields'; -import { transactionTraceFields } from '../doc_viewer_transaction_overview/resources/fields'; -import { SpanSummaryField } from '../doc_viewer_span_overview/sub_components/span_summary_field'; -import { TransactionSummaryField } from '../doc_viewer_transaction_overview/sub_components/transaction_summary_field'; +import { transactionTraceFields, spanTraceFields } from '../doc_viewer_overview/resources/fields'; +import { SummaryField } from '../doc_viewer_overview/sub_components/summary_field'; import { getUnifiedDocViewerServices } from '../../../../plugin'; import type { FieldConfiguration } from '../resources/get_field_configuration'; import { FullScreenWaterfall } from './full_screen_waterfall'; @@ -73,7 +71,7 @@ export const Trace = ({ const fieldRows = displayType === 'span' ? spanTraceFields.map((fieldId: string) => ( - )) : transactionTraceFields.map((fieldId: string) => ( - React.ReactNode; } @@ -34,19 +37,18 @@ export function TransactionNameLink({ const { from: timeRangeFrom, to: timeRangeTo } = dataService.query.timefilter.timefilter.getTime(); - const apmLinkToTransactionByNameLocator = urlService.locators.get<{ - serviceName: string; - transactionName: string; - rangeFrom: string; - rangeTo: string; - }>(TRANSACTION_DETAILS_BY_NAME_LOCATOR); + const apmLinkToTransactionByNameLocator = urlService.locators.get( + TRANSACTION_DETAILS_BY_NAME_LOCATOR + ); - const href = apmLinkToTransactionByNameLocator?.getRedirectUrl({ - serviceName, - transactionName, - rangeFrom: timeRangeFrom, - rangeTo: timeRangeTo, - }); + const href = + serviceName && + apmLinkToTransactionByNameLocator?.getRedirectUrl({ + serviceName, + transactionName, + rangeFrom: timeRangeFrom, + rangeTo: timeRangeTo, + }); const routeLinkProps = href ? getRouterLinkProps({ href, diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/hooks/use_latency_chart.test.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/hooks/use_latency_chart.test.tsx new file mode 100644 index 0000000000000..0853238381943 --- /dev/null +++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/hooks/use_latency_chart.test.tsx @@ -0,0 +1,199 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { renderHook, waitFor } from '@testing-library/react'; +import { useLatencyChart } from './use_latency_chart'; +import { getUnifiedDocViewerServices } from '../../../../../plugin'; +import type { EuiThemeComputed } from '@elastic/eui'; + +jest.mock('../../../../../plugin', () => ({ + getUnifiedDocViewerServices: jest.fn(), +})); + +const mockHttpPost = jest.fn(); +const mockAddDanger = jest.fn(); +const mockTimefilter = { + getAbsoluteTime: () => ({ + from: '2023-01-01T00:00:00.000Z', + to: '2023-01-01T01:00:00.000Z', + }), +}; + +const mockTheme: EuiThemeComputed = { + colors: { + vis: { + euiColorVis1: 'euiColorVis1', + }, + }, +} as EuiThemeComputed; + +(getUnifiedDocViewerServices as jest.Mock).mockReturnValue({ + core: { + http: { + post: mockHttpPost, + }, + notifications: { + toasts: { + addDanger: mockAddDanger, + }, + }, + }, + data: { + query: { + timefilter: { + timefilter: mockTimefilter, + }, + }, + }, +}); + +jest.mock('@elastic/eui', () => { + const originalModule = jest.requireActual('@elastic/eui'); + return { + ...originalModule, + useEuiTheme: () => ({ euiTheme: mockTheme }), + }; +}); + +beforeEach(() => { + jest.clearAllMocks(); +}); + +describe('useLatencyChart', () => { + describe('Spans', () => { + const params = { + spanName: 'test-span', + serviceName: 'test-service', + }; + + describe('when parameters are NOT missing', () => { + it('should fetch and set data successfully', async () => { + mockHttpPost.mockResolvedValue({ + overallHistogram: [{ x: 1, y: 2 }], + percentileThresholdValue: 456, + }); + + const { result } = renderHook(() => useLatencyChart(params)); + + await waitFor(() => !result.current.loading); + + expect(result.current.loading).toBe(false); + expect(result.current.hasError).toBe(false); + expect(result.current.data).toBeDefined(); + expect(result.current.data?.distributionChartData).toHaveLength(1); + expect(result.current.data?.percentileThresholdValue).toBe(456); + }); + }); + + describe('when parameters are missing', () => { + it('should return null data and stop loading', async () => { + const { result } = renderHook(() => + useLatencyChart({ + spanName: '', + serviceName: '', + }) + ); + + await waitFor(() => !result.current.loading); + + expect(result.current.loading).toBe(false); + expect(result.current.hasError).toBe(false); + expect(result.current.data).toBeUndefined(); + expect(mockHttpPost).not.toHaveBeenCalled(); + }); + }); + + describe('when there is an error', () => { + it('should handle error and show toast', async () => { + mockHttpPost.mockRejectedValue(new Error('Fetch error')); + + const { result } = renderHook(() => useLatencyChart(params)); + + await waitFor(() => !result.current.loading); + + expect(result.current.loading).toBe(false); + expect(result.current.hasError).toBe(true); + expect(result.current.data).toBeUndefined(); + expect(mockAddDanger).toHaveBeenCalledWith( + expect.objectContaining({ + title: 'An error occurred while fetching the latency histogram', + text: 'Fetch error', + }) + ); + }); + }); + }); + + describe('Transactions', () => { + const params = { + transactionName: 'test-name', + transactionType: 'test-type', + serviceName: 'test-service', + }; + + describe('when parameters are NOT missing', () => { + it('should fetch and set data successfully', async () => { + mockHttpPost.mockResolvedValue({ + overallHistogram: [{ x: 1, y: 2 }], + percentileThresholdValue: 123, + }); + + const { result } = renderHook(() => useLatencyChart(params)); + + await waitFor(() => !result.current.loading); + await waitFor(() => !!result.current.data); + + expect(result.current.loading).toBe(false); + expect(result.current.hasError).toBe(false); + expect(result.current.data).toBeDefined(); + expect(result.current.data?.distributionChartData).toHaveLength(1); + expect(result.current.data?.percentileThresholdValue).toBe(123); + }); + }); + + describe('when parameters are missing', () => { + it('should return null data and stop loading ', async () => { + const { result } = renderHook(() => + useLatencyChart({ + transactionName: '', + transactionType: '', + serviceName: '', + }) + ); + + await waitFor(() => !result.current.loading); + + expect(result.current.loading).toBe(false); + expect(result.current.hasError).toBe(false); + expect(result.current.data).toBeUndefined(); + expect(mockHttpPost).not.toHaveBeenCalled(); + }); + }); + + describe('when an error occurs', () => { + it('should handle error and show toast', async () => { + mockHttpPost.mockRejectedValue(new Error('Fetch error')); + + const { result } = renderHook(() => useLatencyChart(params)); + + await waitFor(() => !result.current.loading); + + expect(result.current.loading).toBe(false); + expect(result.current.hasError).toBe(true); + expect(result.current.data).toBeUndefined(); + expect(mockAddDanger).toHaveBeenCalledWith( + expect.objectContaining({ + title: 'An error occurred while fetching the latency histogram', + text: 'Fetch error', + }) + ); + }); + }); + }); +}); diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/hooks/use_transaction_latency_chart/index.ts b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/hooks/use_latency_chart.tsx similarity index 50% rename from src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/hooks/use_transaction_latency_chart/index.ts rename to src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/hooks/use_latency_chart.tsx index 878f19e95c8aa..4b835e526cc4f 100644 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/hooks/use_transaction_latency_chart/index.ts +++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/hooks/use_latency_chart.tsx @@ -13,10 +13,10 @@ import { useEffect } from 'react'; import type { EuiThemeComputed } from '@elastic/eui'; import { useEuiTheme } from '@elastic/eui'; +import type { HistogramItem } from '@kbn/apm-types-shared'; import type { DurationDistributionChartData } from '@kbn/apm-ui-shared'; import { useAbortableAsync } from '@kbn/react-hooks'; -import type { HistogramItem } from '@kbn/apm-types-shared'; -import { getUnifiedDocViewerServices } from '../../../../../../plugin'; +import { getUnifiedDocViewerServices } from '../../../../../plugin'; interface GetTransactionDistributionChartDataParams { euiTheme: EuiThemeComputed; @@ -82,48 +82,133 @@ const getTransactionLatencyChart = ({ }); }; -interface TransactionLatencyChartData { - transactionDistributionChartData: DurationDistributionChartData[]; +export interface LatencyChartData { + distributionChartData: DurationDistributionChartData[]; percentileThresholdValue?: number; } -interface UseTransactionLatencyChartParams { - transactionName: string; - transactionType: string; +interface GetSpanDistributionChartDataParams { + euiTheme: EuiThemeComputed; + spanHistogram?: HistogramItem[]; +} + +export const getSpanDistributionChartData = ({ + euiTheme, + spanHistogram, +}: GetSpanDistributionChartDataParams): DurationDistributionChartData[] => + Array.isArray(spanHistogram) + ? [ + { + id: i18n.translate( + 'unifiedDocViewer.observability.traces.useSpanLatencyChart.allSpansLabel', + { + defaultMessage: 'All spans', + } + ), + histogram: spanHistogram, + areaSeriesColor: euiTheme.colors.vis.euiColorVis1, + }, + ] + : []; + +interface GetSpanLatencyChartParams { + core: CoreStart; + signal: AbortSignal; + spanName: string; serviceName: string; + isOtelSpan: boolean; } -export const useTransactionLatencyChart = ({ +const getSpanLatencyChart = ({ + core, + signal, + spanName, + serviceName, + isOtelSpan, +}: GetSpanLatencyChartParams): Promise<{ + overallHistogram?: HistogramItem[]; + percentileThresholdValue?: number; +}> => { + const { data } = getUnifiedDocViewerServices(); + const timeFilter = data.query.timefilter.timefilter.getAbsoluteTime(); + + return core.http.post('/internal/apm/latency/overall_distribution/spans', { + body: JSON.stringify({ + spanName, + serviceName, + chartType: 'spanLatency', + isOtel: isOtelSpan, + end: timeFilter.to, + environment: 'ENVIRONMENT_ALL', + kuery: '', + percentileThreshold: 95, + start: timeFilter.from, + }), + signal, + }); +}; + +interface UseLatencyChartParams { + spanName?: string; + serviceName?: string; + transactionName?: string; + transactionType?: string; + isOtelSpan?: boolean; +} + +export const useLatencyChart = ({ + spanName, + serviceName, transactionName, transactionType, - serviceName, -}: UseTransactionLatencyChartParams) => { + isOtelSpan = false, +}: UseLatencyChartParams) => { const { core } = getUnifiedDocViewerServices(); const { euiTheme } = useEuiTheme(); - const { loading, value, error } = useAbortableAsync( + const { loading, value, error } = useAbortableAsync( async ({ signal }) => { - if (!transactionName || !transactionType || !serviceName) { - return null; + if (!serviceName) { + return undefined; } - const result = await getTransactionLatencyChart({ - core, - signal, - transactionName, - transactionType, - serviceName, - }); + if (transactionName && transactionType) { + const result = await getTransactionLatencyChart({ + core, + signal, + transactionName, + transactionType, + serviceName, + }); + + return { + distributionChartData: getTransactionDistributionChartData({ + euiTheme, + transactionHistogram: result.overallHistogram, + }), + percentileThresholdValue: result.percentileThresholdValue, + }; + } - return { - transactionDistributionChartData: getTransactionDistributionChartData({ - euiTheme, - transactionHistogram: result.overallHistogram, - }), - percentileThresholdValue: result.percentileThresholdValue, - }; + if (spanName) { + const result = await getSpanLatencyChart({ + core, + signal, + spanName, + serviceName, + isOtelSpan, + }); + + return { + distributionChartData: getSpanDistributionChartData({ + euiTheme, + spanHistogram: result.overallHistogram, + }), + percentileThresholdValue: result.percentileThresholdValue, + }; + } }, - [core, euiTheme, serviceName, transactionName, transactionType] + [core, euiTheme, serviceName, spanName] ); useEffect(() => { @@ -140,5 +225,9 @@ export const useTransactionLatencyChart = ({ } }, [error, core]); - return { loading, hasError: !!error, data: value as TransactionLatencyChartData | null }; + return { + loading, + hasError: !!error, + data: value, + }; }; diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/hooks/use_root_span/use_root_span.test.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/hooks/use_root_span.test.tsx similarity index 95% rename from src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/hooks/use_root_span/use_root_span.test.tsx rename to src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/hooks/use_root_span.test.tsx index c6e34b8e934a8..8c277d30b9b39 100644 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/hooks/use_root_span/use_root_span.test.tsx +++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/hooks/use_root_span.test.tsx @@ -10,15 +10,15 @@ import React from 'react'; import { renderHook, waitFor } from '@testing-library/react'; import { lastValueFrom } from 'rxjs'; -import { getUnifiedDocViewerServices } from '../../../../../../plugin'; -import { RootSpanProvider, useRootSpanContext } from '.'; +import { getUnifiedDocViewerServices } from '../../../../../plugin'; +import { RootSpanProvider, useRootSpanContext } from './use_root_span'; import { TRANSACTION_DURATION_FIELD, TRANSACTION_NAME_FIELD } from '@kbn/discover-utils'; -jest.mock('../../../../../../plugin', () => ({ +jest.mock('../../../../../plugin', () => ({ getUnifiedDocViewerServices: jest.fn(), })); -jest.mock('../../../hooks/use_data_sources', () => ({ +jest.mock('../../hooks/use_data_sources', () => ({ useDataSourcesContext: () => ({ indexes: { apm: { traces: 'test-index' } }, }), diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/hooks/use_root_span/index.ts b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/hooks/use_root_span.ts similarity index 97% rename from src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/hooks/use_root_span/index.ts rename to src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/hooks/use_root_span.ts index 00e550d5fb6f1..12b3902aa21cd 100644 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/hooks/use_root_span/index.ts +++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/hooks/use_root_span.ts @@ -23,8 +23,8 @@ import { TRANSACTION_ID_FIELD, TRANSACTION_NAME_FIELD, } from '@kbn/discover-utils'; -import { getUnifiedDocViewerServices } from '../../../../../../plugin'; -import { useDataSourcesContext } from '../../../hooks/use_data_sources'; +import { getUnifiedDocViewerServices } from '../../../../../plugin'; +import { useDataSourcesContext } from '../../hooks/use_data_sources'; interface UseTransactionPrams { traceId?: string; diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/hooks/use_root_transaction/use_root_transaction.test.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/hooks/use_root_transaction.test.tsx similarity index 95% rename from src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/hooks/use_root_transaction/use_root_transaction.test.tsx rename to src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/hooks/use_root_transaction.test.tsx index 620c644e4074e..b12fdc7547ee7 100644 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/hooks/use_root_transaction/use_root_transaction.test.tsx +++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/hooks/use_root_transaction.test.tsx @@ -10,8 +10,8 @@ import React from 'react'; import { renderHook, waitFor } from '@testing-library/react'; import { lastValueFrom } from 'rxjs'; -import { getUnifiedDocViewerServices } from '../../../../../../plugin'; -import { RootTransactionProvider, useRootTransactionContext } from '.'; +import { getUnifiedDocViewerServices } from '../../../../../plugin'; +import { RootTransactionProvider, useRootTransactionContext } from './use_root_transaction'; import { SERVICE_NAME_FIELD, SPAN_ID_FIELD, @@ -20,7 +20,7 @@ import { TRANSACTION_NAME_FIELD, } from '@kbn/discover-utils'; -jest.mock('../../../../../../plugin', () => ({ +jest.mock('../../../../../plugin', () => ({ getUnifiedDocViewerServices: jest.fn(), })); @@ -32,7 +32,7 @@ jest.mock('rxjs', () => { }; }); -jest.mock('../../../hooks/use_data_sources', () => ({ +jest.mock('../../hooks/use_data_sources', () => ({ useDataSourcesContext: () => ({ indexes: { apm: { traces: 'test-index' } }, }), diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/hooks/use_root_transaction/index.ts b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/hooks/use_root_transaction.ts similarity index 96% rename from src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/hooks/use_root_transaction/index.ts rename to src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/hooks/use_root_transaction.ts index fc23b57b7d0c0..516858b488581 100644 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/hooks/use_root_transaction/index.ts +++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/hooks/use_root_transaction.ts @@ -20,8 +20,8 @@ import { } from '@kbn/discover-utils'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { lastValueFrom } from 'rxjs'; -import { getUnifiedDocViewerServices } from '../../../../../../plugin'; -import { useDataSourcesContext } from '../../../hooks/use_data_sources'; +import { getUnifiedDocViewerServices } from '../../../../../plugin'; +import { useDataSourcesContext } from '../../hooks/use_data_sources'; interface UseRootTransactionParams { traceId: string; diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/index.ts b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/index.ts similarity index 87% rename from src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/index.ts rename to src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/index.ts index b21d64c34471d..741b806df2e6b 100644 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/index.ts +++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/index.ts @@ -7,8 +7,8 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { SpanOverview } from './span_overview'; +import { Overview } from './overview'; // Required for usage in React.lazy // eslint-disable-next-line import/no-default-export -export default SpanOverview; +export default Overview; diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/lazy_doc_viewer_obs_traces_span_overview.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/lazy_doc_viewer_obs_traces_overview.tsx similarity index 88% rename from src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/lazy_doc_viewer_obs_traces_span_overview.tsx rename to src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/lazy_doc_viewer_obs_traces_overview.tsx index 73119adbb2b02..351eb85dcae00 100644 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/lazy_doc_viewer_obs_traces_span_overview.tsx +++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/lazy_doc_viewer_obs_traces_overview.tsx @@ -11,7 +11,7 @@ import React from 'react'; import { EuiDelayRender, EuiSkeletonText } from '@elastic/eui'; import { dynamic } from '@kbn/shared-ux-utility'; -export const UnifiedDocViewerObservabilityTracesSpanOverview = dynamic(() => import('.'), { +export const UnifiedDocViewerObservabilityTracesOverview = dynamic(() => import('.'), { fallback: ( diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/span_overview.stories.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/overview.stories.tsx similarity index 62% rename from src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/span_overview.stories.tsx rename to src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/overview.stories.tsx index f90dd3c0abaf8..8a4635b56a38b 100644 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/span_overview.stories.tsx +++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/overview.stories.tsx @@ -11,14 +11,16 @@ import type { Meta, StoryObj } from '@storybook/react'; import type { UnifiedDocViewerStorybookArgs } from '../../../../../.storybook/preview'; import minimalAPMFixture from '../../../../__fixtures__/span_apm_minimal.json'; import minimalOtelFixture from '../../../../__fixtures__/span_otel_minimal.json'; +import httpServerApmFixture from '../../../../__fixtures__/transaction_http_server_apm.json'; +import httpServerOtelFixture from '../../../../__fixtures__/transaction_http_server_otel.json'; import redisClientOtelFixture from '../../../../__fixtures__/span_otel_redis_client.json'; -import { SpanOverview, type SpanOverviewProps } from './span_overview'; +import { Overview, type OverviewProps } from './overview'; -type Args = UnifiedDocViewerStorybookArgs; +type Args = UnifiedDocViewerStorybookArgs; const meta = { - title: 'Span overview', - component: SpanOverview, -} satisfies Meta; + title: 'Trace Overview', + component: Overview, +} satisfies Meta; export default meta; type Story = StoryObj; @@ -55,3 +57,26 @@ export const RedisClientOtel: Story = { }, tags: ['otel', 'db', 'redis', 'client', 'span'], }; + +/** + * APM HTTP transaction + */ +export const ApmHttpServer: Story = { + name: 'APM HTTP server transaction', + args: { + hit: httpServerApmFixture, + }, + tags: ['transaction', 'span', 'http', 'server', 'apm'], +}; + +/** + * OpenTelemetry HTTP server span. + * Processed by the elasticapmprocessor to add APM transaction and span attributes. + */ +export const OtelHttpServer: Story = { + name: 'OpenTelemetry HTTP server transaction', + args: { + hit: httpServerOtelFixture, + }, + tags: ['otel', 'transaction', 'span', 'http', 'server', 'apm'], +}; diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/span_overview.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/overview.tsx similarity index 65% rename from src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/span_overview.tsx rename to src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/overview.tsx index 7e24ef141aef0..59133578191ce 100644 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/span_overview.tsx +++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/overview.tsx @@ -15,12 +15,16 @@ import { SPAN_ID_FIELD, SPAN_NAME_FIELD, TRACE_ID_FIELD, + TRANSACTION_DURATION_FIELD, TRANSACTION_ID_FIELD, - getSpanDocumentOverview, + getTraceDocumentOverview, + getFlattenedTraceDocumentOverview, + TRANSACTION_NAME_FIELD, + TRANSACTION_TYPE_FIELD, } from '@kbn/discover-utils'; import type { TraceIndexes } from '@kbn/discover-utils/src'; -import { getFlattenedSpanDocumentOverview } from '@kbn/discover-utils/src'; import type { DocViewRenderProps } from '@kbn/unified-doc-viewer/types'; +import { ProcessorEvent } from '@kbn/apm-types-shared'; import React, { useMemo, useState } from 'react'; import { css } from '@emotion/react'; import { useDataViewFields } from '../../../../hooks/use_data_view_fields'; @@ -28,27 +32,27 @@ import { FieldActionsProvider } from '../../../../hooks/use_field_actions'; import { getUnifiedDocViewerServices } from '../../../../plugin'; import { SpanLinks } from '../components/span_links'; import { Trace } from '../components/trace'; -import { RootTransactionProvider } from '../doc_viewer_transaction_overview/hooks/use_root_transaction'; -import { DataSourcesProvider } from '../hooks/use_data_sources'; import { RootSpanProvider } from './hooks/use_root_span'; -import { allSpanFields, spanFields } from './resources/fields'; -import { getSpanFieldConfiguration } from './resources/get_span_field_configuration'; -import { SpanDurationSummary } from './sub_components/span_duration_summary'; -import { SpanSummaryField } from './sub_components/span_summary_field'; -import { SpanSummaryTitle } from './sub_components/span_summary_title'; +import { spanAndTransactionFields, traceFields } from './resources/fields'; +import { getFieldConfiguration } from './resources/get_field_configuration'; +import { DurationSummary } from './sub_components/duration_summary'; +import { SummaryField } from './sub_components/summary_field'; +import { SummaryTitle } from './sub_components/summary_title'; +import { RootTransactionProvider } from './hooks/use_root_transaction'; +import { DataSourcesProvider } from '../hooks/use_data_sources'; import { getTabContentAvailableHeight, DEFAULT_MARGIN_BOTTOM, } from '../../../doc_viewer_source/get_height'; import { TraceContextLogEvents } from '../components/trace_context_log_events'; -export type SpanOverviewProps = DocViewRenderProps & { +export type OverviewProps = DocViewRenderProps & { indexes: TraceIndexes; showWaterfall?: boolean; showActions?: boolean; }; -export function SpanOverview({ +export function Overview({ columns, hit, filter, @@ -60,28 +64,36 @@ export function SpanOverview({ dataView, columnsMeta, decreaseAvailableHeightBy = DEFAULT_MARGIN_BOTTOM, -}: SpanOverviewProps) { +}: OverviewProps) { const [containerRef, setContainerRef] = useState(null); const { fieldFormats } = getUnifiedDocViewerServices(); const { formattedDoc, flattenedDoc } = useMemo( () => ({ - formattedDoc: getSpanDocumentOverview(hit, { dataView, fieldFormats }), - flattenedDoc: getFlattenedSpanDocumentOverview(hit), + formattedDoc: getTraceDocumentOverview(hit, { dataView, fieldFormats }), + flattenedDoc: getFlattenedTraceDocumentOverview(hit), }), [dataView, fieldFormats, hit] ); - const { dataViewFields } = useDataViewFields({ fields: allSpanFields, dataView, columnsMeta }); + const { dataViewFields } = useDataViewFields({ fields: traceFields, dataView, columnsMeta }); const fieldConfigurations = useMemo( - () => getSpanFieldConfiguration({ attributes: formattedDoc, flattenedDoc }), + () => getFieldConfiguration({ attributes: formattedDoc, flattenedDoc }), [formattedDoc, flattenedDoc] ); - const isOtelSpan = - flattenedDoc[SPAN_DURATION_FIELD] == null && flattenedDoc[OTEL_DURATION] != null; + const id = flattenedDoc[TRANSACTION_ID_FIELD] || flattenedDoc[SPAN_ID_FIELD]!; + const formattedId = formattedDoc[TRANSACTION_ID_FIELD] || formattedDoc[SPAN_ID_FIELD]; + const formattedName = formattedDoc[TRANSACTION_NAME_FIELD] || formattedDoc[SPAN_NAME_FIELD]; + + const displayType = formattedDoc[TRANSACTION_NAME_FIELD] + ? ProcessorEvent.transaction + : ProcessorEvent.span; + + const apmDurationField = + flattenedDoc[TRANSACTION_DURATION_FIELD] ?? flattenedDoc[SPAN_DURATION_FIELD]; - const spanDuration = isOtelSpan - ? flattenedDoc[OTEL_DURATION]! * 0.001 - : flattenedDoc[SPAN_DURATION_FIELD]; + const isOtelSpan = apmDurationField == null && flattenedDoc[OTEL_DURATION] != null; + + const duration = isOtelSpan ? flattenedDoc[OTEL_DURATION]! * 0.001 : apmDurationField; const traceId = flattenedDoc[TRACE_ID_FIELD]; const transactionId = flattenedDoc[TRANSACTION_ID_FIELD]; @@ -116,17 +128,19 @@ export function SpanOverview({ > - - {spanFields.map((fieldId) => ( - ( + - {spanDuration && ( + {duration && ( - @@ -152,18 +168,20 @@ export function SpanOverview({ - - - - + {spanId && ( + + + + + )} => { return { ...getCommonFieldConfiguration({ attributes, flattenedDoc }), @@ -98,5 +100,28 @@ export const getSpanFieldConfiguration = ({ value: flattenedDoc[SPAN_SUBTYPE_FIELD], formattedValue: attributes[SPAN_SUBTYPE_FIELD], }, + [USER_AGENT_NAME_FIELD]: { + title: i18n.translate('unifiedDocViewer.observability.traces.details.userAgent.title', { + defaultMessage: 'User agent', + }), + content: (value, formattedValue) => ( + + ), + value: flattenedDoc[USER_AGENT_NAME_FIELD], + formattedValue: attributes[USER_AGENT_NAME_FIELD], + }, + [USER_AGENT_VERSION_FIELD]: { + title: i18n.translate( + 'unifiedDocViewer.observability.traces.details.userAgentVersion.title', + { + defaultMessage: 'User agent version', + } + ), + content: (value, formattedValue) => ( + + ), + value: flattenedDoc[USER_AGENT_VERSION_FIELD], + formattedValue: attributes[USER_AGENT_VERSION_FIELD], + }, }; }; diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/sub_components/dependency_name_link.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/sub_components/dependency_name_link.tsx similarity index 90% rename from src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/sub_components/dependency_name_link.tsx rename to src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/sub_components/dependency_name_link.tsx index cf4a934c54bae..4ddfd1c180aab 100644 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/sub_components/dependency_name_link.tsx +++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/sub_components/dependency_name_link.tsx @@ -8,7 +8,10 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; -import { DEPENDENCY_OVERVIEW_LOCATOR_ID } from '@kbn/deeplinks-observability'; +import { + type DependencyOverviewParams, + DEPENDENCY_OVERVIEW_LOCATOR_ID, +} from '@kbn/deeplinks-observability'; import { getRouterLinkProps } from '@kbn/router-utils'; import React from 'react'; import { SpanIcon } from '@kbn/apm-ui-shared'; @@ -18,7 +21,7 @@ interface DependencyNameLinkProps { dependencyName: string; spanType?: string; spanSubtype?: string; - environment: string; + environment?: string; formattedDependencyName?: React.ReactNode; } @@ -39,12 +42,9 @@ export function DependencyNameLink({ const { from: timeRangeFrom, to: timeRangeTo } = dataService.query.timefilter.timefilter.getTime(); - const apmLinkToDependencyOverviewLocator = urlService.locators.get<{ - dependencyName: string; - environment: string; - rangeFrom: string; - rangeTo: string; - }>(DEPENDENCY_OVERVIEW_LOCATOR_ID); + const apmLinkToDependencyOverviewLocator = urlService.locators.get( + DEPENDENCY_OVERVIEW_LOCATOR_ID + ); const href = apmLinkToDependencyOverviewLocator?.getRedirectUrl({ dependencyName, diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/sub_components/span_duration_summary/index.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/sub_components/duration_summary.tsx similarity index 80% rename from src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/sub_components/span_duration_summary/index.tsx rename to src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/sub_components/duration_summary.tsx index a1f1444323f57..3074ab075ebde 100644 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/sub_components/span_duration_summary/index.tsx +++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/sub_components/duration_summary.tsx @@ -12,32 +12,38 @@ import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiText, EuiTitle } from import { i18n } from '@kbn/i18n'; import { Duration, DurationDistributionChart } from '@kbn/apm-ui-shared'; import { ProcessorEvent } from '@kbn/apm-types-shared'; -import { useRootSpanContext } from '../../hooks/use_root_span'; -import { useSpanLatencyChart } from '../../hooks/use_span_latency_chart'; -import { FieldWithoutActions } from '../../../components/field_without_actions'; -import { Section } from '../../../components/section'; +import { useRootSpanContext } from '../hooks/use_root_span'; +import { useLatencyChart } from '../hooks/use_latency_chart'; +import { FieldWithoutActions } from '../../components/field_without_actions'; +import { Section } from '../../components/section'; -export interface SpanDurationSummaryProps { - spanDuration: number; - spanName: string; - serviceName: string; +export interface DurationSummaryProps { + duration: number; + spanName?: string; + serviceName?: string; + transactionName?: string; + transactionType?: string; isOtelSpan: boolean; } -export function SpanDurationSummary({ - spanDuration, +export function DurationSummary({ + duration, spanName, serviceName, + transactionName, + transactionType, isOtelSpan, -}: SpanDurationSummaryProps) { +}: DurationSummaryProps) { const { trace, loading } = useRootSpanContext(); const { data: latencyChartData, loading: latencyChartLoading, hasError: latencyChartHasError, - } = useSpanLatencyChart({ + } = useLatencyChart({ spanName, serviceName, + transactionName, + transactionType, isOtelSpan, }); @@ -67,7 +73,7 @@ export function SpanDurationSummary({ > @@ -90,9 +96,9 @@ export function SpanDurationSummary({ {(latencyChartLoading || latencyChartData) && ( ({ + FieldHoverActionPopover: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), +})); + +jest.mock('../../components/highlight_field', () => ({ + HighlightField: ({ + value, + formattedValue, + as, + }: { + value?: string; + formattedValue?: string; + as?: keyof JSX.IntrinsicElements; + }) => { + const Tag = as || 'span'; + return ; + }, +})); + +jest.mock('../../components/transaction_name_link', () => ({ + TransactionNameLink: ({ renderContent }: { renderContent: () => React.ReactNode }) => ( +
{renderContent()}
+ ), +})); + +describe('SummaryTitle', () => { + describe('Spans', () => { + afterAll(() => { + jest.clearAllMocks(); + }); + it('renders spanName with formattedName and id with formattedId', () => { + const { getByText, container } = render( + + ); + + expect(container.querySelector('strong')).toHaveTextContent('Test Span'); + expect(container.querySelector('strong')?.innerHTML).toBe('Test Span'); + + expect(getByText('123')).toBeInTheDocument(); + expect(container.querySelector('span')?.innerHTML).toBe('123'); + }); + + it('renders only id with formattedId when spanName is not provided', () => { + const { getByText, container } = render( + + ); + + expect(getByText('123')).toBeInTheDocument(); + expect(container.querySelector('h2')?.innerHTML).toBe('123'); + }); + + it('renders FieldHoverActionPopover for spanName and id', () => { + const { getByText } = render( + + ); + + expect(getByText('Test Span')).toBeInTheDocument(); + expect(getByText('123')).toBeInTheDocument(); + }); + + it('renders FieldHoverActionPopover if showActions is undefined', () => { + const { container } = render(); + + expect( + container.querySelector(`[data-test-subj="${fieldHoverActionPopoverDataTestSubj}"]`) + ).not.toBeNull(); + }); + + it('renders FieldHoverActionPopover if showActions is true', () => { + const { container } = render( + + ); + + expect( + container.querySelector(`[data-test-subj="${fieldHoverActionPopoverDataTestSubj}"]`) + ).not.toBeNull(); + }); + + it('does not render FieldHoverActionPopover if showActions is false', () => { + const { container } = render( + + ); + + expect( + container.querySelector(`[data-test-subj="${fieldHoverActionPopoverDataTestSubj}"]`) + ).toBeNull(); + }); + }); + + describe('Transactions', () => { + afterAll(() => { + jest.clearAllMocks(); + }); + + it('renders transactionName with formattedName and id with formattedId', () => { + const { getByText, container } = render( + + ); + + expect(container.querySelector('strong')).toHaveTextContent('Test Transaction'); + expect(container.querySelector('strong')?.innerHTML).toBe('Test Transaction'); + + expect(getByText('123')).toBeInTheDocument(); + expect(container.querySelector('span')?.innerHTML).toBe('123'); + }); + + it('renders only serviceName when transactionName is not provided', () => { + const { getByText } = render( + + ); + + expect(getByText('Test Service')).toBeInTheDocument(); + + expect(getByText('123')).toBeInTheDocument(); + }); + + it('renders FieldHoverActionPopover for transactionName and id', () => { + const { getByText } = render( + + ); + + expect(getByText('Test Transaction')).toBeInTheDocument(); + expect(getByText('123')).toBeInTheDocument(); + }); + + it('renders TransactionNameLink with transactionName and formattedName', () => { + const { getByText, container } = render( + + ); + + expect(getByText('Test Transaction')).toBeInTheDocument(); + expect(container.querySelector('strong')?.innerHTML).toBe('Test Transaction'); + }); + + it('renders id with formattedId when provided', () => { + const { getByText, container } = render( + + ); + + expect(getByText('123')).toBeInTheDocument(); + expect(container.querySelector('span')?.innerHTML).toBe('123'); + }); + + it('renders FieldHoverActionPopover if showActions is undefined', () => { + const { container } = render(); + + expect( + container.querySelector(`[data-test-subj="${fieldHoverActionPopoverDataTestSubj}"]`) + ).not.toBeNull(); + }); + + it('renders FieldHoverActionPopover if showActions is true', () => { + const { container } = render(); + + expect( + container.querySelector(`[data-test-subj="${fieldHoverActionPopoverDataTestSubj}"]`) + ).not.toBeNull(); + }); + + it('does not render FieldHoverActionPopover if showActions is false', () => { + const { container } = render(); + + expect( + container.querySelector(`[data-test-subj="${fieldHoverActionPopoverDataTestSubj}"]`) + ).toBeNull(); + }); + }); +}); diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/sub_components/summary_title.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/sub_components/summary_title.tsx new file mode 100644 index 0000000000000..b7e465376b43e --- /dev/null +++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_overview/sub_components/summary_title.tsx @@ -0,0 +1,126 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { EuiTitle } from '@elastic/eui'; +import { + SERVICE_NAME_FIELD, + SPAN_ID_FIELD, + SPAN_NAME_FIELD, + TRANSACTION_ID_FIELD, + TRANSACTION_NAME_FIELD, +} from '@kbn/discover-utils'; +import React from 'react'; +import { FieldHoverActionPopover } from '../../components/field_with_actions/field_hover_popover_action'; +import { HighlightField } from '../../components/highlight_field'; +import { TransactionNameLink } from '../../components/transaction_name_link'; + +export interface SummaryTitleProps { + spanName?: string; + transactionName?: string; + formattedName?: string; + serviceName?: string; + id?: string; + formattedId?: string; + showActions?: boolean; +} + +const FieldContent = ({ + children, + field, + title, + value, + showActions, +}: { + children: React.ReactNode; + field: string; + title: string; + value: string; + showActions: boolean; +}) => { + return showActions ? ( + + <>{children} + + ) : ( + <>{children} + ); +}; + +const Title = ({ isTitle, children }: { isTitle: boolean; children: React.ReactNode }) => { + return isTitle ? ( + +

{children}

+
+ ) : ( + children + ); +}; + +export const SummaryTitle = ({ + spanName, + transactionName, + serviceName, + id, + formattedId, + formattedName, + showActions = true, +}: SummaryTitleProps) => { + const name = transactionName || spanName; + + const idField = transactionName ? TRANSACTION_ID_FIELD : SPAN_ID_FIELD; + const nameField = transactionName ? TRANSACTION_NAME_FIELD : SPAN_NAME_FIELD; + + let nameContent; + + if (name) { + nameContent = ( + + + <HighlightField textSize="m" value={name} formattedValue={formattedName} as="strong"> + {transactionName && serviceName + ? ({ content }) => ( + <TransactionNameLink + serviceName={serviceName} + transactionName={transactionName} + renderContent={() => content} + /> + ) + : undefined} + </HighlightField> + + + ); + } else if (serviceName) { + nameContent = ( + + {serviceName} + + ); + } + + const idContent = id ? ( + + + <HighlightField value={id} formattedValue={formattedId} /> + + + ) : undefined; + + return ( + <> + {nameContent} + {idContent} + + ); +}; diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/hooks/use_span_latency_chart/index.ts b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/hooks/use_span_latency_chart/index.ts deleted file mode 100644 index cf2ceed37fe9a..0000000000000 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/hooks/use_span_latency_chart/index.ts +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import type { CoreStart } from '@kbn/core/public'; -import { i18n } from '@kbn/i18n'; -import { useEffect } from 'react'; - -import type { EuiThemeComputed } from '@elastic/eui'; -import { useEuiTheme } from '@elastic/eui'; -import type { HistogramItem } from '@kbn/apm-types-shared'; -import type { DurationDistributionChartData } from '@kbn/apm-ui-shared'; -import { useAbortableAsync } from '@kbn/react-hooks'; -import { getUnifiedDocViewerServices } from '../../../../../../plugin'; - -interface GetSpanDistributionChartDataParams { - euiTheme: EuiThemeComputed; - spanHistogram?: HistogramItem[]; -} - -export const getSpanDistributionChartData = ({ - euiTheme, - spanHistogram, -}: GetSpanDistributionChartDataParams): DurationDistributionChartData[] => - Array.isArray(spanHistogram) - ? [ - { - id: i18n.translate( - 'unifiedDocViewer.observability.traces.useSpanLatencyChart.allSpansLabel', - { - defaultMessage: 'All spans', - } - ), - histogram: spanHistogram, - areaSeriesColor: euiTheme.colors.vis.euiColorVis1, - }, - ] - : []; - -interface GetLatencyChartParams { - core: CoreStart; - signal: AbortSignal; - spanName: string; - serviceName: string; - isOtelSpan: boolean; -} - -const getSpanLatencyChart = ({ - core, - signal, - spanName, - serviceName, - isOtelSpan, -}: GetLatencyChartParams): Promise<{ - overallHistogram?: HistogramItem[]; - percentileThresholdValue?: number; -}> => { - const { data } = getUnifiedDocViewerServices(); - const timeFilter = data.query.timefilter.timefilter.getAbsoluteTime(); - - return core.http.post('/internal/apm/latency/overall_distribution/spans', { - body: JSON.stringify({ - spanName, - serviceName, - chartType: 'spanLatency', - isOtel: isOtelSpan, - end: timeFilter.to, - environment: 'ENVIRONMENT_ALL', - kuery: '', - percentileThreshold: 95, - start: timeFilter.from, - }), - signal, - }); -}; - -export interface SpanLatencyChartData { - spanDistributionChartData: DurationDistributionChartData[]; - percentileThresholdValue?: number; -} - -interface UseSpanLatencyChartParams { - spanName: string; - serviceName: string; - isOtelSpan?: boolean; -} - -export const useSpanLatencyChart = ({ - spanName, - serviceName, - isOtelSpan = false, -}: UseSpanLatencyChartParams) => { - const { core } = getUnifiedDocViewerServices(); - const { euiTheme } = useEuiTheme(); - - const { loading, value, error } = useAbortableAsync( - async ({ signal }) => { - if (!spanName || !serviceName) { - return null; - } - - const result = await getSpanLatencyChart({ - core, - signal, - spanName, - serviceName, - isOtelSpan, - }); - - return { - spanDistributionChartData: getSpanDistributionChartData({ - euiTheme, - spanHistogram: result.overallHistogram, - }), - percentileThresholdValue: result.percentileThresholdValue, - }; - }, - [core, euiTheme, serviceName, spanName] - ); - - useEffect(() => { - if (error) { - core.notifications.toasts.addDanger({ - title: i18n.translate( - 'unifiedDocViewer.observability.traces.useTransactionLatencyChart.error', - { - defaultMessage: 'An error occurred while fetching the latency histogram', - } - ), - text: error.message, - }); - } - }, [error, core]); - - return { loading, hasError: !!error, data: value as SpanLatencyChartData | null }; -}; diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/hooks/use_span_latency_chart/use_span_latency_chart.test.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/hooks/use_span_latency_chart/use_span_latency_chart.test.tsx deleted file mode 100644 index a266607ceda76..0000000000000 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/hooks/use_span_latency_chart/use_span_latency_chart.test.tsx +++ /dev/null @@ -1,130 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { renderHook, waitFor } from '@testing-library/react'; -import { useSpanLatencyChart } from '.'; -import { getUnifiedDocViewerServices } from '../../../../../../plugin'; -import type { EuiThemeComputed } from '@elastic/eui'; - -jest.mock('../../../../../../plugin', () => ({ - getUnifiedDocViewerServices: jest.fn(), -})); - -const mockHttpPost = jest.fn(); -const mockAddDanger = jest.fn(); -const mockTimefilter = { - getAbsoluteTime: () => ({ - from: '2023-01-01T00:00:00.000Z', - to: '2023-01-01T01:00:00.000Z', - }), -}; - -const mockTheme: EuiThemeComputed = { - colors: { - vis: { - euiColorVis1: 'euiColorVis1', - }, - }, -} as EuiThemeComputed; - -(getUnifiedDocViewerServices as jest.Mock).mockReturnValue({ - core: { - http: { - post: mockHttpPost, - }, - notifications: { - toasts: { - addDanger: mockAddDanger, - }, - }, - }, - data: { - query: { - timefilter: { - timefilter: mockTimefilter, - }, - }, - }, -}); - -jest.mock('@elastic/eui', () => { - const originalModule = jest.requireActual('@elastic/eui'); - return { - ...originalModule, - useEuiTheme: () => ({ euiTheme: mockTheme }), - }; -}); - -beforeEach(() => { - jest.clearAllMocks(); -}); - -describe('useSpanLatencyChart', () => { - const params = { - spanName: 'test-span', - serviceName: 'test-service', - }; - - describe('when parameters are NOT missing', () => { - it('should fetch and set data successfully', async () => { - mockHttpPost.mockResolvedValue({ - overallHistogram: [{ x: 1, y: 2 }], - percentileThresholdValue: 456, - }); - - const { result } = renderHook(() => useSpanLatencyChart(params)); - - await waitFor(() => !result.current.loading); - - expect(result.current.loading).toBe(false); - expect(result.current.hasError).toBe(false); - expect(result.current.data).toBeDefined(); - expect(result.current.data?.spanDistributionChartData).toHaveLength(1); - expect(result.current.data?.percentileThresholdValue).toBe(456); - }); - }); - - describe('when parameters are missing', () => { - it('should return null data and stop loading', async () => { - const { result } = renderHook(() => - useSpanLatencyChart({ - spanName: '', - serviceName: '', - }) - ); - - await waitFor(() => !result.current.loading); - - expect(result.current.loading).toBe(false); - expect(result.current.hasError).toBe(false); - expect(result.current.data).toBeNull(); - expect(mockHttpPost).not.toHaveBeenCalled(); - }); - }); - - describe('when there is an error', () => { - it('should handle error and show toast', async () => { - mockHttpPost.mockRejectedValue(new Error('Fetch error')); - - const { result } = renderHook(() => useSpanLatencyChart(params)); - - await waitFor(() => !result.current.loading); - - expect(result.current.loading).toBe(false); - expect(result.current.hasError).toBe(true); - expect(result.current.data).toBeUndefined(); - expect(mockAddDanger).toHaveBeenCalledWith( - expect.objectContaining({ - title: 'An error occurred while fetching the latency histogram', - text: 'Fetch error', - }) - ); - }); - }); -}); diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/sub_components/span_summary_title.test.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/sub_components/span_summary_title.test.tsx deleted file mode 100644 index dd98c2d16951f..0000000000000 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/sub_components/span_summary_title.test.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ -import React from 'react'; -import { render } from '@testing-library/react'; -import { SpanSummaryTitle } from './span_summary_title'; - -const fieldHoverActionPopoverDataTestSubj = 'FieldHoverActionPopover'; - -jest.mock('../../components/field_with_actions/field_hover_popover_action', () => ({ - FieldHoverActionPopover: ({ children }: { children: React.ReactNode }) => ( -
{children}
- ), -})); - -jest.mock('../../components/highlight_field', () => ({ - HighlightField: ({ - value, - formattedValue, - as, - }: { - value?: string; - formattedValue?: string; - as?: keyof JSX.IntrinsicElements; - }) => { - const Tag = as || 'span'; - return ; - }, -})); - -describe('SpanSummaryTitle', () => { - afterAll(() => { - jest.clearAllMocks(); - }); - it('renders spanName with formattedSpanName and id with formattedId', () => { - const { getByText, container } = render( - - ); - - expect(container.querySelector('strong')).toHaveTextContent('Test Span'); - expect(container.querySelector('strong')?.innerHTML).toBe('Test Span'); - - expect(getByText('123')).toBeInTheDocument(); - expect(container.querySelector('span')?.innerHTML).toBe('123'); - }); - - it('renders only id with formattedId when spanName is not provided', () => { - const { getByText, container } = render( - - ); - - expect(getByText('123')).toBeInTheDocument(); - expect(container.querySelector('h2')?.innerHTML).toBe('123'); - }); - - it('renders FieldHoverActionPopover for spanName and id', () => { - const { getByText } = render( - - ); - - expect(getByText('Test Span')).toBeInTheDocument(); - expect(getByText('123')).toBeInTheDocument(); - }); - - it('renders FieldHoverActionPopover if showActions is undefined', () => { - const { container } = render( - - ); - - expect( - container.querySelector(`[data-test-subj="${fieldHoverActionPopoverDataTestSubj}"]`) - ).not.toBeNull(); - }); - - it('renders FieldHoverActionPopover if showActions is true', () => { - const { container } = render( - - ); - - expect( - container.querySelector(`[data-test-subj="${fieldHoverActionPopoverDataTestSubj}"]`) - ).not.toBeNull(); - }); - - it('does not render FieldHoverActionPopover if showActions is false', () => { - const { container } = render( - - ); - - expect( - container.querySelector(`[data-test-subj="${fieldHoverActionPopoverDataTestSubj}"]`) - ).toBeNull(); - }); -}); diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/sub_components/span_summary_title.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/sub_components/span_summary_title.tsx deleted file mode 100644 index 40870ee0cc688..0000000000000 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_span_overview/sub_components/span_summary_title.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { EuiTitle } from '@elastic/eui'; -import { SPAN_ID_FIELD, SPAN_NAME_FIELD } from '@kbn/discover-utils'; -import React from 'react'; -import { FieldHoverActionPopover } from '../../components/field_with_actions/field_hover_popover_action'; -import { HighlightField } from '../../components/highlight_field'; - -export interface SpanSummaryTitleProps { - spanName?: string; - formattedSpanName?: string; - spanId: string; - formattedSpanId: string; - showActions?: boolean; -} - -export const SpanSummaryTitle = ({ - spanName, - spanId, - formattedSpanId, - formattedSpanName, - showActions = true, -}: SpanSummaryTitleProps) => { - const FieldContent = ({ - children, - field, - title, - value, - }: { - children: React.ReactNode; - field: string; - title: string; - value: string; - showActions: boolean; - }) => { - return showActions ? ( - - <>{children} - - ) : ( - <>{children} - ); - }; - - return spanName ? ( - <> -
- - -

- -

-
-
-
- - - - - ) : ( - - - - - - ); -}; diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/hooks/use_transaction_latency_chart/use_transaction_latency_chart.test.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/hooks/use_transaction_latency_chart/use_transaction_latency_chart.test.tsx deleted file mode 100644 index da400fe0b2759..0000000000000 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/hooks/use_transaction_latency_chart/use_transaction_latency_chart.test.tsx +++ /dev/null @@ -1,133 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { renderHook, waitFor } from '@testing-library/react'; -import { useTransactionLatencyChart } from '.'; -import { getUnifiedDocViewerServices } from '../../../../../../plugin'; -import type { EuiThemeComputed } from '@elastic/eui'; - -jest.mock('../../../../../../plugin', () => ({ - getUnifiedDocViewerServices: jest.fn(), -})); - -const mockHttpPost = jest.fn(); -const mockAddDanger = jest.fn(); -const mockTimefilter = { - getAbsoluteTime: () => ({ - from: '2023-01-01T00:00:00.000Z', - to: '2023-01-01T01:00:00.000Z', - }), -}; - -const mockTheme: EuiThemeComputed = { - colors: { - vis: { - euiColorVis1: 'euiColorVis1', - }, - }, -} as EuiThemeComputed; - -(getUnifiedDocViewerServices as jest.Mock).mockReturnValue({ - core: { - http: { - post: mockHttpPost, - }, - notifications: { - toasts: { - addDanger: mockAddDanger, - }, - }, - }, - data: { - query: { - timefilter: { - timefilter: mockTimefilter, - }, - }, - }, -}); - -jest.mock('@elastic/eui', () => { - const originalModule = jest.requireActual('@elastic/eui'); - return { - ...originalModule, - useEuiTheme: () => ({ euiTheme: mockTheme }), - }; -}); - -beforeEach(() => { - jest.clearAllMocks(); -}); - -describe('useTransactionLatencyChart', () => { - const params = { - transactionName: 'test-name', - transactionType: 'test-type', - serviceName: 'test-service', - }; - - describe('when parameters are NOT missing', () => { - it('should fetch and set data successfully', async () => { - mockHttpPost.mockResolvedValue({ - overallHistogram: [{ x: 1, y: 2 }], - percentileThresholdValue: 123, - }); - - const { result } = renderHook(() => useTransactionLatencyChart(params)); - - await waitFor(() => !result.current.loading); - await waitFor(() => !!result.current.data); - - expect(result.current.loading).toBe(false); - expect(result.current.hasError).toBe(false); - expect(result.current.data).toBeDefined(); - expect(result.current.data?.transactionDistributionChartData).toHaveLength(1); - expect(result.current.data?.percentileThresholdValue).toBe(123); - }); - }); - - describe('when parameters are missing', () => { - it('should return null data and stop loading ', async () => { - const { result } = renderHook(() => - useTransactionLatencyChart({ - transactionName: '', - transactionType: '', - serviceName: '', - }) - ); - - await waitFor(() => !result.current.loading); - - expect(result.current.loading).toBe(false); - expect(result.current.hasError).toBe(false); - expect(result.current.data).toBeNull(); - expect(mockHttpPost).not.toHaveBeenCalled(); - }); - }); - - describe('when an error occurs', () => { - it('should handle error and show toast', async () => { - mockHttpPost.mockRejectedValue(new Error('Fetch error')); - - const { result } = renderHook(() => useTransactionLatencyChart(params)); - - await waitFor(() => !result.current.loading); - - expect(result.current.loading).toBe(false); - expect(result.current.hasError).toBe(true); - expect(result.current.data).toBeUndefined(); - expect(mockAddDanger).toHaveBeenCalledWith( - expect.objectContaining({ - title: 'An error occurred while fetching the latency histogram', - text: 'Fetch error', - }) - ); - }); - }); -}); diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/index.ts b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/index.ts deleted file mode 100644 index 73ea789c72cca..0000000000000 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { TransactionOverview } from './transaction_overview'; - -// Required for usage in React.lazy -// eslint-disable-next-line import/no-default-export -export default TransactionOverview; diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/lazy_doc_viewer_obs_traces_transaction_overview.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/lazy_doc_viewer_obs_traces_transaction_overview.tsx deleted file mode 100644 index c8237adb1b131..0000000000000 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/lazy_doc_viewer_obs_traces_transaction_overview.tsx +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import React from 'react'; -import { EuiDelayRender, EuiSkeletonText } from '@elastic/eui'; -import { dynamic } from '@kbn/shared-ux-utility'; - -export const UnifiedDocViewerObservabilityTracesTransactionOverview = dynamic(() => import('.'), { - fallback: ( - - - - ), -}); diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/resources/fields.ts b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/resources/fields.ts deleted file mode 100644 index 6ab4efe7513e5..0000000000000 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/resources/fields.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { - HTTP_RESPONSE_STATUS_CODE_FIELD, - SERVICE_NAME_FIELD, - TIMESTAMP_FIELD, - TRACE_ID_FIELD, - USER_AGENT_NAME_FIELD, - USER_AGENT_VERSION_FIELD, -} from '@kbn/discover-utils'; - -export const transactionFields = [ - SERVICE_NAME_FIELD, - TIMESTAMP_FIELD, - HTTP_RESPONSE_STATUS_CODE_FIELD, - USER_AGENT_NAME_FIELD, - USER_AGENT_VERSION_FIELD, -]; - -export const transactionTraceFields = [TRACE_ID_FIELD]; - -export const allTransactionFields = [...transactionFields, ...transactionTraceFields]; diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/resources/get_transaction_field_configuration.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/resources/get_transaction_field_configuration.tsx deleted file mode 100644 index ddd9a61d9a7dc..0000000000000 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/resources/get_transaction_field_configuration.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import type { TransactionDocumentOverview } from '@kbn/discover-utils'; -import { USER_AGENT_NAME_FIELD, USER_AGENT_VERSION_FIELD } from '@kbn/discover-utils'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import type { FieldConfiguration } from '../../resources/get_field_configuration'; -import { getCommonFieldConfiguration } from '../../resources/get_field_configuration'; -import { HighlightField } from '../../components/highlight_field'; - -export const getTransactionFieldConfiguration = ({ - attributes, - flattenedDoc, -}: { - attributes: TransactionDocumentOverview; - flattenedDoc: TransactionDocumentOverview; -}): Record => { - return { - ...getCommonFieldConfiguration({ attributes, flattenedDoc }), - [USER_AGENT_NAME_FIELD]: { - title: i18n.translate('unifiedDocViewer.observability.traces.details.userAgent.title', { - defaultMessage: 'User agent', - }), - content: (value, formattedValue) => ( - - ), - value: flattenedDoc[USER_AGENT_NAME_FIELD], - formattedValue: attributes[USER_AGENT_NAME_FIELD], - }, - [USER_AGENT_VERSION_FIELD]: { - title: i18n.translate( - 'unifiedDocViewer.observability.traces.details.userAgentVersion.title', - { - defaultMessage: 'User agent version', - } - ), - content: (value, formattedValue) => ( - - ), - value: flattenedDoc[USER_AGENT_VERSION_FIELD], - formattedValue: attributes[USER_AGENT_VERSION_FIELD], - }, - }; -}; diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/sub_components/transaction_duration_summary/index.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/sub_components/transaction_duration_summary/index.tsx deleted file mode 100644 index 33560ae1d7b82..0000000000000 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/sub_components/transaction_duration_summary/index.tsx +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiText, EuiTitle } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { Duration, DurationDistributionChart } from '@kbn/apm-ui-shared'; -import { ProcessorEvent } from '@kbn/apm-types-shared'; -import { useRootTransactionContext } from '../../hooks/use_root_transaction'; -import { useTransactionLatencyChart } from '../../hooks/use_transaction_latency_chart'; -import { Section } from '../../../components/section'; -import { FieldWithoutActions } from '../../../components/field_without_actions'; - -export interface TransactionDurationSummaryProps { - transactionDuration: number; - transactionName: string; - transactionType: string; - serviceName: string; -} - -export function TransactionDurationSummary({ - transactionDuration, - transactionName, - transactionType, - serviceName, -}: TransactionDurationSummaryProps) { - const { transaction: rootTransaction, loading: rootTransactionLoading } = - useRootTransactionContext(); - - const { - data: latencyChartData, - loading: latencyChartLoading, - hasError: latencyChartHasError, - } = useTransactionLatencyChart({ - transactionName, - transactionType, - serviceName, - }); - - return ( -
- <> - - - - - - - - - - -

- {i18n.translate( - 'unifiedDocViewer.observability.traces.docViewerTransactionOverview.spanDurationSummary.latency.title', - { - defaultMessage: 'Latency', - } - )} -

-
- - {(latencyChartLoading || latencyChartData) && ( - - )} -
-
- -
- ); -} diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/sub_components/transaction_summary_field.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/sub_components/transaction_summary_field.tsx deleted file mode 100644 index 4d65e995aef73..0000000000000 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/sub_components/transaction_summary_field.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { EuiHorizontalRule } from '@elastic/eui'; -import React from 'react'; -import type { DataViewField } from '@kbn/data-views-plugin/common'; -import type { FieldConfiguration } from '../../resources/get_field_configuration'; -import { FieldWithActions } from '../../components/field_with_actions/field_with_actions'; - -export interface TransactionSummaryFieldProps { - fieldId: string; - fieldConfiguration: FieldConfiguration; - fieldMapping?: DataViewField; - showActions?: boolean; -} - -export function TransactionSummaryField({ - fieldConfiguration, - fieldId, - fieldMapping, - showActions = true, -}: TransactionSummaryFieldProps) { - if (!fieldConfiguration.value) { - return null; - } - - return ( - <> - -
- {fieldConfiguration.content(fieldConfiguration.value, fieldConfiguration.formattedValue)} -
-
- - - ); -} diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/sub_components/transaction_summary_title.test.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/sub_components/transaction_summary_title.test.tsx deleted file mode 100644 index 5a6575521f330..0000000000000 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/sub_components/transaction_summary_title.test.tsx +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ -import React from 'react'; -import { render } from '@testing-library/react'; -import { TransactionSummaryTitle } from './transaction_summary_title'; - -const fieldHoverActionPopoverDataTestSubj = 'FieldHoverActionPopover'; - -jest.mock('../../components/field_with_actions/field_hover_popover_action', () => ({ - FieldHoverActionPopover: ({ children }: { children: React.ReactNode }) => ( -
{children}
- ), -})); - -jest.mock('../../components/highlight_field', () => ({ - HighlightField: ({ - value, - formattedValue, - as, - children, - }: { - value?: string; - formattedValue?: string; - as?: keyof JSX.IntrinsicElements; - children?: (props: { content: React.ReactNode }) => React.ReactNode; - }) => { - const Tag = as || 'span'; - const content = ; - return children ? children({ content }) : content; - }, -})); - -jest.mock('../../components/transaction_name_link', () => ({ - TransactionNameLink: ({ renderContent }: { renderContent: () => React.ReactNode }) => ( -
{renderContent()}
- ), -})); - -describe('TransactionSummaryTitle', () => { - afterAll(() => { - jest.clearAllMocks(); - }); - - it('renders transactionName with formattedTransactionName and id with formattedId', () => { - const { getByText, container } = render( - - ); - - expect(container.querySelector('strong')).toHaveTextContent('Test Transaction'); - expect(container.querySelector('strong')?.innerHTML).toBe('Test Transaction'); - - expect(getByText('123')).toBeInTheDocument(); - expect(container.querySelector('span')?.innerHTML).toBe('123'); - }); - - it('renders only serviceName when transactionName is not provided', () => { - const { getByText } = render( - - ); - - expect(getByText('Test Service')).toBeInTheDocument(); - - expect(getByText('123')).toBeInTheDocument(); - }); - - it('renders FieldHoverActionPopover for transactionName and id', () => { - const { getByText } = render( - - ); - - expect(getByText('Test Transaction')).toBeInTheDocument(); - expect(getByText('123')).toBeInTheDocument(); - }); - - it('renders TransactionNameLink with transactionName and formattedTransactionName', () => { - const { getByText, container } = render( - - ); - - expect(getByText('Test Transaction')).toBeInTheDocument(); - expect(container.querySelector('strong')?.innerHTML).toBe('Test Transaction'); - }); - - it('renders id with formattedId when provided', () => { - const { getByText, container } = render( - - ); - - expect(getByText('123')).toBeInTheDocument(); - expect(container.querySelector('span')?.innerHTML).toBe('123'); - }); - - it('renders FieldHoverActionPopover if showActions is undefined', () => { - const { container } = render(); - - expect( - container.querySelector(`[data-test-subj="${fieldHoverActionPopoverDataTestSubj}"]`) - ).not.toBeNull(); - }); - - it('renders FieldHoverActionPopover if showActions is true', () => { - const { container } = render( - - ); - - expect( - container.querySelector(`[data-test-subj="${fieldHoverActionPopoverDataTestSubj}"]`) - ).not.toBeNull(); - }); - - it('does not render FieldHoverActionPopover if showActions is false', () => { - const { container } = render( - - ); - - expect( - container.querySelector(`[data-test-subj="${fieldHoverActionPopoverDataTestSubj}"]`) - ).toBeNull(); - }); -}); diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/sub_components/transaction_summary_title.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/sub_components/transaction_summary_title.tsx deleted file mode 100644 index 09df9675ca866..0000000000000 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/sub_components/transaction_summary_title.tsx +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { EuiTitle } from '@elastic/eui'; -import { - SERVICE_NAME_FIELD, - TRANSACTION_ID_FIELD, - TRANSACTION_NAME_FIELD, -} from '@kbn/discover-utils'; -import React from 'react'; -import { FieldHoverActionPopover } from '../../components/field_with_actions/field_hover_popover_action'; -import { HighlightField } from '../../components/highlight_field'; -import { TransactionNameLink } from '../../components/transaction_name_link'; - -export interface TransactionSummaryTitleProps { - serviceName: string; - transactionName?: string; - formattedTransactionName?: string; - id?: string; - formattedId?: string; - showActions?: boolean; -} - -export const TransactionSummaryTitle = ({ - serviceName, - transactionName, - id, - formattedId, - formattedTransactionName, - showActions = true, -}: TransactionSummaryTitleProps) => { - const FieldContent = ({ - children, - field, - title, - value, - }: { - children: React.ReactNode; - field: string; - title: string; - value: string; - showActions: boolean; - }) => { - return showActions ? ( - - <>{children} - - ) : ( - <>{children} - ); - }; - - return ( - <> - -

- {transactionName ? ( - - - {({ content }) => ( - content} - /> - )} - - - ) : ( - - {serviceName} - - )} -

-
- - {id && ( - - - - )} - - ); -}; diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/transaction_overview.stories.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/transaction_overview.stories.tsx deleted file mode 100644 index beb8748ef6046..0000000000000 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/transaction_overview.stories.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import type { Meta, StoryObj } from '@storybook/react'; -import type { UnifiedDocViewerStorybookArgs } from '../../../../../.storybook/preview'; -import httpServerApmFixture from '../../../../__fixtures__/transaction_http_server_apm.json'; -import httpServerOtelFixture from '../../../../__fixtures__/transaction_http_server_otel.json'; -import { TransactionOverview, type TransactionOverviewProps } from './transaction_overview'; - -type Args = UnifiedDocViewerStorybookArgs; -const meta = { - title: 'Transaction overview', - component: TransactionOverview, -} satisfies Meta; -export default meta; - -type Story = StoryObj; - -/** - * APM HTTP transaction - */ -export const ApmHttpServer: Story = { - name: 'APM HTTP server transaction', - args: { - hit: httpServerApmFixture, - }, - tags: ['transaction', 'span', 'http', 'server', 'apm'], -}; - -/** - * OpenTelemetry HTTP server span. - * Processed by the elasticapmprocessor to add APM transaction and span attributes. - */ -export const OtelHttpServer: Story = { - name: 'OpenTelemetry HTTP server transaction', - args: { - hit: httpServerOtelFixture, - }, - tags: ['otel', 'transaction', 'span', 'http', 'server', 'apm'], -}; diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/transaction_overview.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/transaction_overview.tsx deleted file mode 100644 index 7f172de466507..0000000000000 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/doc_viewer_transaction_overview/transaction_overview.tsx +++ /dev/null @@ -1,176 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import React, { useMemo, useState } from 'react'; -import type { DocViewRenderProps } from '@kbn/unified-doc-viewer/types'; -import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { - SERVICE_NAME_FIELD, - TRACE_ID_FIELD, - TRANSACTION_DURATION_FIELD, - TRANSACTION_NAME_FIELD, - TRANSACTION_TYPE_FIELD, - getTransactionDocumentOverview, - TRANSACTION_ID_FIELD, -} from '@kbn/discover-utils'; -import type { TraceIndexes } from '@kbn/discover-utils/src'; -import { getFlattenedTransactionDocumentOverview } from '@kbn/discover-utils/src'; -import { css } from '@emotion/react'; -import { ProcessorEvent } from '@kbn/apm-types-shared'; -import { useDataViewFields } from '../../../../hooks/use_data_view_fields'; -import { FieldActionsProvider } from '../../../../hooks/use_field_actions'; -import { transactionFields, allTransactionFields } from './resources/fields'; -import { getTransactionFieldConfiguration } from './resources/get_transaction_field_configuration'; -import { TransactionSummaryField } from './sub_components/transaction_summary_field'; -import { TransactionDurationSummary } from './sub_components/transaction_duration_summary'; -import { RootTransactionProvider } from './hooks/use_root_transaction'; -import { Trace } from '../components/trace'; -import { TransactionSummaryTitle } from './sub_components/transaction_summary_title'; -import { getUnifiedDocViewerServices } from '../../../../plugin'; -import { DataSourcesProvider } from '../hooks/use_data_sources'; -import { - DEFAULT_MARGIN_BOTTOM, - getTabContentAvailableHeight, -} from '../../../doc_viewer_source/get_height'; -import { TraceContextLogEvents } from '../components/trace_context_log_events'; -import { SpanLinks } from '../components/span_links'; - -export type TransactionOverviewProps = DocViewRenderProps & { - indexes: TraceIndexes; - showWaterfall?: boolean; - showActions?: boolean; -}; - -export function TransactionOverview({ - columns, - hit, - filter, - onAddColumn, - onRemoveColumn, - indexes, - showWaterfall = true, - showActions = true, - dataView, - columnsMeta, - decreaseAvailableHeightBy = DEFAULT_MARGIN_BOTTOM, -}: TransactionOverviewProps) { - const [containerRef, setContainerRef] = useState(null); - const { fieldFormats } = getUnifiedDocViewerServices(); - const { formattedDoc, flattenedDoc } = useMemo( - () => ({ - formattedDoc: getTransactionDocumentOverview(hit, { dataView, fieldFormats }), - flattenedDoc: getFlattenedTransactionDocumentOverview(hit), - }), - [dataView, fieldFormats, hit] - ); - const { dataViewFields } = useDataViewFields({ - fields: allTransactionFields, - dataView, - columnsMeta, - }); - const transactionDuration = flattenedDoc[TRANSACTION_DURATION_FIELD]; - const fieldConfigurations = useMemo( - () => getTransactionFieldConfiguration({ attributes: formattedDoc, flattenedDoc }), - [formattedDoc, flattenedDoc] - ); - const traceId = flattenedDoc[TRACE_ID_FIELD]; - const transactionId = flattenedDoc[TRANSACTION_ID_FIELD]; - - const containerHeight = containerRef - ? getTabContentAvailableHeight(containerRef, decreaseAvailableHeightBy) - : 0; - - return ( - - - - - - - - - - {transactionFields.map((fieldId) => ( - - ))} - - {transactionDuration !== undefined && ( - - - - )} - - {traceId && transactionId && ( - <> - - - - )} - - - - - - - - - - - - - ); -} diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/resources/get_field_configuration.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/resources/get_field_configuration.tsx index 589b51e711469..8616aa6d7c2f1 100644 --- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/resources/get_field_configuration.tsx +++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/resources/get_field_configuration.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { SpanDocumentOverview, TransactionDocumentOverview } from '@kbn/discover-utils'; +import type { TraceDocumentOverview } from '@kbn/discover-utils'; import { SERVICE_NAME_FIELD, TRACE_ID_FIELD, @@ -39,8 +39,8 @@ export const getCommonFieldConfiguration = ({ attributes, flattenedDoc, }: { - attributes: TransactionDocumentOverview | SpanDocumentOverview; - flattenedDoc: TransactionDocumentOverview | SpanDocumentOverview; + attributes: TraceDocumentOverview; + flattenedDoc: TraceDocumentOverview; }): Record => { return { [TRANSACTION_NAME_FIELD]: { diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/index.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/index.tsx index 4267d00e0c862..6b63f74a345c5 100644 --- a/src/platform/plugins/shared/unified_doc_viewer/public/index.tsx +++ b/src/platform/plugins/shared/unified_doc_viewer/public/index.tsx @@ -36,7 +36,6 @@ export type { } from './components/doc_viewer_logs_overview/logs_overview'; export { UnifiedDocViewerLogsOverview } from './components/lazy_doc_viewer_logs_overview'; -export { UnifiedDocViewerObservabilityTracesSpanOverview } from './components/observability/traces/doc_viewer_span_overview/lazy_doc_viewer_obs_traces_span_overview'; -export { UnifiedDocViewerObservabilityTracesTransactionOverview } from './components/observability/traces/doc_viewer_transaction_overview/lazy_doc_viewer_obs_traces_transaction_overview'; +export { UnifiedDocViewerObservabilityTracesOverview } from './components/observability/traces/doc_viewer_overview/lazy_doc_viewer_obs_traces_overview'; export { UnifiedDocViewerObservabilityAttributesOverview } from './components/observability/attributes/doc_viewer_attributes_overview/lazy_doc_viewer_obs_attributes_overview'; export const plugin = () => new UnifiedDocViewerPublicPlugin();