[8.x] [One Discover] Display stacktrace in the logs overview tab (#204521) (#208115)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[One Discover] Display stacktrace in the logs overview tab
(#204521)](https://github.com/elastic/kibana/pull/204521)

<!--- Backport version: 9.6.4 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Giorgos
Bamparopoulos","email":"georgios.bamparopoulos@elastic.co"},"sourceCommit":{"committedDate":"2025-01-22T16:06:14Z","message":"[One
Discover] Display stacktrace in the logs overview tab (#204521)\n\n## 📓
Summary\r\nAdds a new section to the overview tab in the log details
flyout in\r\nDiscover to display stacktrace information for logs and
exceptions.\r\n\r\nIn a follow-up, the stacktrace could be moved to a
new tab in the log\r\ndetails flyout and actions can be added to the
stacktrace (and quality)\r\nicons in the document table to open the
relevant sections in the flyout.\r\n\r\nCloses
https://github.com/elastic/kibana/issues/190460\r\n\r\n### APM - Log
stacktrace (library frames)\r\n<img width=\"1470\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/8991f882-d329-4bc5-aa37-424576bcee72\"\r\n/>\r\n\r\n###
APM - Exception (with cause)\r\n<img width=\"1476\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/cfbf24a7-6f82-48f1-b275-5aac977411ac\"\r\n/>\r\n\r\n###
APM - Exception (simple stacktrace)\r\n<img width=\"1474\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/fc0306c4-5fcd-4b74-bb0d-c1784a48d677\"\r\n/>\r\n\r\n###
Apache Tomcat Integration (Catalina) - Stacktrace\r\n<img width=\"1472\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/281f1822-faea-4e2d-9515-c11a9ee12f50\"\r\n/>\r\n\r\n##
📝 Notes for reviewers\r\n- The `@kbn/apm-types` package was marked as
platform / shared as it's\r\nbeing used by
the\r\n[unified_doc_viewer](https://github.com/elastic/kibana/blob/main/src/plugins/unified_doc_viewer/kibana.jsonc)\r\n-
The code used to render stacktraces in APM was moved into a
new\r\n`@kbn/event-stacktrace` package as it is reused in
the\r\n`unified_doc_viewer`\r\n- The code used to render metadata table
in APM was moved into a new\r\n`@kbn/key-value-metadata-table`
package\r\n\r\n## 🧪 Testing instructions\r\nThe deployed environments
have sample logs that can be used (time range:\r\nJan 1, 2025 - now).
For a local setup, please follow the instructions\r\nbelow:\r\n\r\n1.
Ingest sample logs with
stacktraces\r\n([gist](https://gist.github.com/gbamparop/0da21ca7f65b24c4a9c071ce9e9b97b0)).\r\nPlease
note that these are test data and some fields that are not used\r\nby
stacktraces might not be consistent\r\n2. View relevant logs in Discover
(Query: `service.name: \"synth-node-0\"\r\nOR apache_tomcat :*`, Time
range: Jan 1, 2025 - now)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"368475e8e55845e17fd4621c1ae60ba1e983bb8f","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:enhancement","v9.0.0","ci:project-deploy-observability","Team:obs-ux-infra_services","backport:version","v8.18.0"],"title":"[One
Discover] Display stacktrace in the logs overview
tab","number":204521,"url":"https://github.com/elastic/kibana/pull/204521","mergeCommit":{"message":"[One
Discover] Display stacktrace in the logs overview tab (#204521)\n\n## 📓
Summary\r\nAdds a new section to the overview tab in the log details
flyout in\r\nDiscover to display stacktrace information for logs and
exceptions.\r\n\r\nIn a follow-up, the stacktrace could be moved to a
new tab in the log\r\ndetails flyout and actions can be added to the
stacktrace (and quality)\r\nicons in the document table to open the
relevant sections in the flyout.\r\n\r\nCloses
https://github.com/elastic/kibana/issues/190460\r\n\r\n### APM - Log
stacktrace (library frames)\r\n<img width=\"1470\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/8991f882-d329-4bc5-aa37-424576bcee72\"\r\n/>\r\n\r\n###
APM - Exception (with cause)\r\n<img width=\"1476\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/cfbf24a7-6f82-48f1-b275-5aac977411ac\"\r\n/>\r\n\r\n###
APM - Exception (simple stacktrace)\r\n<img width=\"1474\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/fc0306c4-5fcd-4b74-bb0d-c1784a48d677\"\r\n/>\r\n\r\n###
Apache Tomcat Integration (Catalina) - Stacktrace\r\n<img width=\"1472\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/281f1822-faea-4e2d-9515-c11a9ee12f50\"\r\n/>\r\n\r\n##
📝 Notes for reviewers\r\n- The `@kbn/apm-types` package was marked as
platform / shared as it's\r\nbeing used by
the\r\n[unified_doc_viewer](https://github.com/elastic/kibana/blob/main/src/plugins/unified_doc_viewer/kibana.jsonc)\r\n-
The code used to render stacktraces in APM was moved into a
new\r\n`@kbn/event-stacktrace` package as it is reused in
the\r\n`unified_doc_viewer`\r\n- The code used to render metadata table
in APM was moved into a new\r\n`@kbn/key-value-metadata-table`
package\r\n\r\n## 🧪 Testing instructions\r\nThe deployed environments
have sample logs that can be used (time range:\r\nJan 1, 2025 - now).
For a local setup, please follow the instructions\r\nbelow:\r\n\r\n1.
Ingest sample logs with
stacktraces\r\n([gist](https://gist.github.com/gbamparop/0da21ca7f65b24c4a9c071ce9e9b97b0)).\r\nPlease
note that these are test data and some fields that are not used\r\nby
stacktraces might not be consistent\r\n2. View relevant logs in Discover
(Query: `service.name: \"synth-node-0\"\r\nOR apache_tomcat :*`, Time
range: Jan 1, 2025 - now)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"368475e8e55845e17fd4621c1ae60ba1e983bb8f"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/204521","number":204521,"mergeCommit":{"message":"[One
Discover] Display stacktrace in the logs overview tab (#204521)\n\n## 📓
Summary\r\nAdds a new section to the overview tab in the log details
flyout in\r\nDiscover to display stacktrace information for logs and
exceptions.\r\n\r\nIn a follow-up, the stacktrace could be moved to a
new tab in the log\r\ndetails flyout and actions can be added to the
stacktrace (and quality)\r\nicons in the document table to open the
relevant sections in the flyout.\r\n\r\nCloses
https://github.com/elastic/kibana/issues/190460\r\n\r\n### APM - Log
stacktrace (library frames)\r\n<img width=\"1470\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/8991f882-d329-4bc5-aa37-424576bcee72\"\r\n/>\r\n\r\n###
APM - Exception (with cause)\r\n<img width=\"1476\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/cfbf24a7-6f82-48f1-b275-5aac977411ac\"\r\n/>\r\n\r\n###
APM - Exception (simple stacktrace)\r\n<img width=\"1474\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/fc0306c4-5fcd-4b74-bb0d-c1784a48d677\"\r\n/>\r\n\r\n###
Apache Tomcat Integration (Catalina) - Stacktrace\r\n<img width=\"1472\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/281f1822-faea-4e2d-9515-c11a9ee12f50\"\r\n/>\r\n\r\n##
📝 Notes for reviewers\r\n- The `@kbn/apm-types` package was marked as
platform / shared as it's\r\nbeing used by
the\r\n[unified_doc_viewer](https://github.com/elastic/kibana/blob/main/src/plugins/unified_doc_viewer/kibana.jsonc)\r\n-
The code used to render stacktraces in APM was moved into a
new\r\n`@kbn/event-stacktrace` package as it is reused in
the\r\n`unified_doc_viewer`\r\n- The code used to render metadata table
in APM was moved into a new\r\n`@kbn/key-value-metadata-table`
package\r\n\r\n## 🧪 Testing instructions\r\nThe deployed environments
have sample logs that can be used (time range:\r\nJan 1, 2025 - now).
For a local setup, please follow the instructions\r\nbelow:\r\n\r\n1.
Ingest sample logs with
stacktraces\r\n([gist](https://gist.github.com/gbamparop/0da21ca7f65b24c4a9c071ce9e9b97b0)).\r\nPlease
note that these are test data and some fields that are not used\r\nby
stacktraces might not be consistent\r\n2. View relevant logs in Discover
(Query: `service.name: \"synth-node-0\"\r\nOR apache_tomcat :*`, Time
range: Jan 1, 2025 - now)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"368475e8e55845e17fd4621c1ae60ba1e983bb8f"}},{"branch":"8.x","label":"v8.18.0","branchLabelMappingKey":"^v8.18.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Giorgos Bamparopoulos 2025-01-28 15:05:59 +02:00 committed by GitHub
parent 1178850485
commit 7ebef44cf5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
118 changed files with 835 additions and 127 deletions

4
.github/CODEOWNERS vendored
View file

@ -48,7 +48,7 @@ x-pack/solutions/observability/plugins/apm/ftr_e2e @elastic/obs-ux-infra_service
x-pack/solutions/observability/plugins/apm @elastic/obs-ux-infra_services-team
packages/kbn-apm-synthtrace @elastic/obs-ux-infra_services-team @elastic/obs-ux-logs-team
packages/kbn-apm-synthtrace-client @elastic/obs-ux-infra_services-team @elastic/obs-ux-logs-team
x-pack/solutions/observability/packages/kbn-apm-types @elastic/obs-ux-infra_services-team
x-pack/platform/packages/shared/kbn-apm-types @elastic/obs-ux-infra_services-team
src/platform/packages/shared/kbn-apm-utils @elastic/obs-ux-infra_services-team
test/plugin_functional/plugins/app_link_test @elastic/kibana-core
x-pack/test/usage_collection/plugins/application_usage_test @elastic/kibana-core
@ -452,6 +452,7 @@ src/platform/plugins/private/event_annotation_listing @elastic/kibana-visualizat
src/platform/plugins/private/event_annotation @elastic/kibana-visualizations
x-pack/test/plugin_api_integration/plugins/event_log @elastic/response-ops
x-pack/platform/plugins/shared/event_log @elastic/response-ops
x-pack/platform/packages/shared/kbn-event-stacktrace @elastic/obs-ux-infra_services-team @elastic/obs-ux-logs-team
x-pack/solutions/security/packages/expandable-flyout @elastic/security-threat-hunting-investigations
packages/kbn-expect @elastic/kibana-operations @elastic/appex-qa
x-pack/examples/exploratory_view_example @elastic/obs-ux-infra_services-team
@ -565,6 +566,7 @@ test/plugin_functional/plugins/kbn_sample_panel_action @elastic/appex-sharedux
test/plugin_functional/plugins/kbn_top_nav @elastic/kibana-core
test/plugin_functional/plugins/kbn_tp_custom_visualizations @elastic/kibana-visualizations
test/interpreter_functional/plugins/kbn_tp_run_pipeline @elastic/kibana-core
x-pack/platform/packages/shared/kbn-key-value-metadata-table @elastic/obs-ux-infra_services-team @elastic/obs-ux-logs-team
x-pack/test/functional_cors/plugins/kibana_cors_test @elastic/kibana-security
packages/kbn-kibana-manifest-schema @elastic/kibana-operations
src/platform/plugins/private/kibana_overview @elastic/appex-sharedux

View file

@ -187,7 +187,7 @@
"@kbn/apm-data-access-plugin": "link:x-pack/solutions/observability/plugins/apm_data_access",
"@kbn/apm-data-view": "link:src/platform/packages/shared/kbn-apm-data-view",
"@kbn/apm-plugin": "link:x-pack/solutions/observability/plugins/apm",
"@kbn/apm-types": "link:x-pack/solutions/observability/packages/kbn-apm-types",
"@kbn/apm-types": "link:x-pack/platform/packages/shared/kbn-apm-types",
"@kbn/apm-utils": "link:src/platform/packages/shared/kbn-apm-utils",
"@kbn/app-link-test-plugin": "link:test/plugin_functional/plugins/app_link_test",
"@kbn/application-usage-test-plugin": "link:x-pack/test/usage_collection/plugins/application_usage_test",
@ -503,6 +503,7 @@
"@kbn/event-annotation-plugin": "link:src/platform/plugins/private/event_annotation",
"@kbn/event-log-fixture-plugin": "link:x-pack/test/plugin_api_integration/plugins/event_log",
"@kbn/event-log-plugin": "link:x-pack/platform/plugins/shared/event_log",
"@kbn/event-stacktrace": "link:x-pack/platform/packages/shared/kbn-event-stacktrace",
"@kbn/expandable-flyout": "link:x-pack/solutions/security/packages/expandable-flyout",
"@kbn/exploratory-view-example-plugin": "link:x-pack/examples/exploratory_view_example",
"@kbn/exploratory-view-plugin": "link:x-pack/solutions/observability/plugins/exploratory_view",
@ -600,6 +601,7 @@
"@kbn/kbn-top-nav-plugin": "link:test/plugin_functional/plugins/kbn_top_nav",
"@kbn/kbn-tp-custom-visualizations-plugin": "link:test/plugin_functional/plugins/kbn_tp_custom_visualizations",
"@kbn/kbn-tp-run-pipeline-plugin": "link:test/interpreter_functional/plugins/kbn_tp_run_pipeline",
"@kbn/key-value-metadata-table": "link:x-pack/platform/packages/shared/kbn-key-value-metadata-table",
"@kbn/kibana-cors-test-plugin": "link:x-pack/test/functional_cors/plugins/kibana_cors_test",
"@kbn/kibana-overview-plugin": "link:src/platform/plugins/private/kibana_overview",
"@kbn/kibana-react-plugin": "link:src/platform/plugins/shared/kibana_react",

View file

@ -58,8 +58,8 @@ export type LogDocument = Fields &
'cloud.project.id'?: string;
'cloud.instance.id'?: string;
'error.stack_trace'?: string;
'error.exception.stacktrace'?: string;
'error.log.stacktrace'?: string;
'error.exception'?: unknown;
'error.log'?: unknown;
'log.custom': Record<string, unknown>;
'host.geo.location': number[];
'host.ip': string;

View file

@ -144,7 +144,7 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
.defaults({
...commonLongEntryFields,
'error.message': message,
'error.exception.stacktrace': 'Error message in error.exception.stacktrace',
'error.stack_trace': 'Stacktrace',
})
.timestamp(timestamp);
});
@ -174,7 +174,7 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
.defaults({
...commonLongEntryFields,
'event.original': message,
'error.log.stacktrace': 'Error message in error.log.stacktrace',
'error.stack_trace': 'Stacktrace',
'event.start': eventDate,
'event.end': moment(eventDate).add(1, 'm').toDate(),
})
@ -203,7 +203,7 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
.setHostIp(getIpAddress())
.defaults({
...commonLongEntryFields,
'error.stack_trace': 'Error message in error.stack_trace',
'error.stack_trace': 'Stacktrace',
})
.timestamp(timestamp);
});

View file

@ -15,6 +15,7 @@ import {
generateLongId,
generateShortId,
Instance,
LogDocument,
} from '@kbn/apm-synthtrace-client';
import { Scenario } from '../cli/scenario';
import { getSynthtraceEnvironment } from '../lib/utils/get_synthtrace_environment';
@ -26,7 +27,7 @@ const ENVIRONMENT = getSynthtraceEnvironment(__filename);
const alwaysSpikeTransactionName = 'GET /always-spike';
const sometimesSpikeTransactionName = 'GET /sometimes-spike';
const scenario: Scenario<ApmFields> = async ({ logger, ...runOptions }) => {
const scenario: Scenario<LogDocument | ApmFields> = async ({ logger, ...runOptions }) => {
const { isLogsDb } = parseLogsScenarioOpts(runOptions.scenarioOpts);
return {

View file

@ -77,4 +77,5 @@ export const storybookAliases = {
ui_actions_enhanced: 'src/platform/plugins/shared/ui_actions_enhanced/.storybook',
unified_search: 'src/platform/plugins/shared/unified_search/.storybook',
profiling: 'x-pack/solutions/observability/plugins/profiling/.storybook',
event_stacktrace: 'x-pack/platform/packages/shared/kbn-event-stacktrace/.storybook',
};

View file

@ -37,8 +37,8 @@ export interface LogDocument extends DataTableRecord {
'data_stream.dataset': string;
'error.stack_trace'?: string;
'error.exception.stacktrace'?: string;
'error.log.stacktrace'?: string;
'error.exception.stacktrace.abs_path'?: string;
'error.log.stacktrace.abs_path'?: string;
};
}
@ -83,8 +83,8 @@ export interface ResourceFields {
export interface StackTraceFields {
'error.stack_trace'?: string;
'error.exception.stacktrace'?: string;
'error.log.stacktrace'?: string;
'error.exception.stacktrace.abs_path'?: string;
'error.log.stacktrace.abs_path'?: string;
}
export interface SmartFieldGridColumnOptions {

View file

@ -19,6 +19,7 @@ export const TRACE_ID_FIELD = 'trace.id';
export const LOG_FILE_PATH_FIELD = 'log.file.path';
export const DATASTREAM_NAMESPACE_FIELD = 'data_stream.namespace';
export const DATASTREAM_DATASET_FIELD = 'data_stream.dataset';
export const DATASTREAM_TYPE_FIELD = 'data_stream.type';
// Resource Fields
export const AGENT_NAME_FIELD = 'agent.name';
@ -41,5 +42,5 @@ export const DEGRADED_DOCS_FIELDS = [IGNORED_FIELD, IGNORED_FIELD_VALUES_FIELD]
// Error Stacktrace
export const ERROR_STACK_TRACE = 'error.stack_trace';
export const ERROR_EXCEPTION_STACKTRACE = 'error.exception.stacktrace';
export const ERROR_LOG_STACKTRACE = 'error.log.stacktrace';
export const ERROR_EXCEPTION_STACKTRACE_ABS_PATH = 'error.exception.stacktrace.abs_path';
export const ERROR_LOG_STACKTRACE_ABS_PATH = 'error.log.stacktrace.abs_path';

View file

@ -9,19 +9,19 @@
import { getFieldValue, LogDocument, StackTraceFields } from '..';
import {
ERROR_EXCEPTION_STACKTRACE,
ERROR_LOG_STACKTRACE,
ERROR_EXCEPTION_STACKTRACE_ABS_PATH,
ERROR_LOG_STACKTRACE_ABS_PATH,
ERROR_STACK_TRACE,
} from '../field_constants';
export const getStacktraceFields = (doc: LogDocument): StackTraceFields => {
const errorStackTrace = getFieldValue(doc, ERROR_STACK_TRACE);
const errorExceptionStackTrace = getFieldValue(doc, ERROR_EXCEPTION_STACKTRACE);
const errorLogStackTrace = getFieldValue(doc, ERROR_LOG_STACKTRACE);
const errorExceptionStackTrace = getFieldValue(doc, ERROR_EXCEPTION_STACKTRACE_ABS_PATH);
const errorLogStackTrace = getFieldValue(doc, ERROR_LOG_STACKTRACE_ABS_PATH);
return {
[ERROR_STACK_TRACE]: errorStackTrace,
[ERROR_EXCEPTION_STACKTRACE]: errorExceptionStackTrace,
[ERROR_LOG_STACKTRACE]: errorLogStackTrace,
[ERROR_EXCEPTION_STACKTRACE_ABS_PATH]: errorExceptionStackTrace,
[ERROR_LOG_STACKTRACE_ABS_PATH]: errorLogStackTrace,
};
};

View file

@ -20,7 +20,8 @@
"fieldsMetadata"
],
"requiredBundles": [
"kibanaUtils"
"kibanaUtils",
"kibanaReact"
]
}
}
}

View file

@ -12,11 +12,13 @@ import { DocViewRenderProps } from '@kbn/unified-doc-viewer/types';
import { getLogDocumentOverview } from '@kbn/discover-utils';
import { EuiHorizontalRule, EuiSpacer } from '@elastic/eui';
import { ObservabilityLogsAIAssistantFeatureRenderDeps } from '@kbn/discover-shared-plugin/public';
import { getStacktraceFields, LogDocument } from '@kbn/discover-utils/src';
import { LogsOverviewHeader } from './logs_overview_header';
import { LogsOverviewHighlights } from './logs_overview_highlights';
import { FieldActionsProvider } from '../../hooks/use_field_actions';
import { getUnifiedDocViewerServices } from '../../plugin';
import { LogsOverviewDegradedFields } from './logs_overview_degraded_fields';
import { LogsOverviewStacktraceSection } from './logs_overview_stacktrace_section';
export type LogsOverviewProps = DocViewRenderProps & {
renderAIAssistant?: (deps: ObservabilityLogsAIAssistantFeatureRenderDeps) => JSX.Element;
@ -34,6 +36,8 @@ export function LogsOverview({
const { fieldFormats } = getUnifiedDocViewerServices();
const parsedDoc = getLogDocumentOverview(hit, { dataView, fieldFormats });
const LogsOverviewAIAssistant = renderAIAssistant;
const stacktraceFields = getStacktraceFields(hit as LogDocument);
const isStacktraceAvailable = Object.values(stacktraceFields).some(Boolean);
return (
<FieldActionsProvider
@ -47,6 +51,7 @@ export function LogsOverview({
<EuiHorizontalRule margin="xs" />
<LogsOverviewHighlights formattedDoc={parsedDoc} flattenedDoc={hit.flattened} />
<LogsOverviewDegradedFields rawDoc={hit.raw} />
{isStacktraceAvailable && <LogsOverviewStacktraceSection hit={hit} dataView={dataView} />}
{LogsOverviewAIAssistant && <LogsOverviewAIAssistant doc={hit} />}
</FieldActionsProvider>
);

View file

@ -0,0 +1,55 @@
/*
* 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 { EuiAccordion, EuiHorizontalRule, EuiTitle, useGeneratedHtmlId } from '@elastic/eui';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import { DataTableRecord } from '@kbn/discover-utils';
import { i18n } from '@kbn/i18n';
import React from 'react';
import type { DataView } from '@kbn/data-views-plugin/public';
import { StacktraceContent } from './sub_components/stacktrace/stacktrace_content';
const stacktraceAccordionTitle = i18n.translate(
'unifiedDocViewer.docView.logsOverview.accordion.title.stacktrace',
{
defaultMessage: 'Stacktrace',
}
);
export function LogsOverviewStacktraceSection({
hit,
dataView,
}: {
hit: DataTableRecord;
dataView: DataView;
}) {
const accordionId = useGeneratedHtmlId({
prefix: stacktraceAccordionTitle,
});
return (
<>
<EuiAccordion
id={accordionId}
buttonContent={
<EuiTitle size="xs">
<p>{stacktraceAccordionTitle}</p>
</EuiTitle>
}
paddingSize="m"
initialIsOpen={false}
data-test-subj="unifiedDocViewLogsOverviewStacktraceAccordion"
>
<EuiThemeProvider>
<StacktraceContent hit={hit} dataView={dataView} />
</EuiThemeProvider>
</EuiAccordion>
<EuiHorizontalRule margin="xs" />
</>
);
}

View file

@ -0,0 +1,233 @@
/*
* 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 { mountWithIntl } from '@kbn/test-jest-helpers';
import '@kbn/code-editor-mock/jest_helper';
import * as hooks from '../../../../hooks/use_es_doc_search';
import { EuiLoadingSpinner } from '@elastic/eui';
import { buildDataTableRecord } from '@kbn/discover-utils';
import { ApmStacktrace } from './apm_stacktrace';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import { ExceptionStacktrace, PlaintextStacktrace, Stacktrace } from '@kbn/event-stacktrace';
const mockDataView = {
getComputedFields: () => [],
} as never;
describe('APM Stacktrace component', () => {
test('renders loading state', () => {
jest.spyOn(hooks, 'useEsDocSearch').mockImplementation(() => [0, null, () => {}]);
const comp = mountWithIntl(
<ApmStacktrace
hit={{
raw: { _id: '1', _index: 'index1' },
flattened: {},
id: '',
}}
dataView={mockDataView}
/>
);
const loadingSpinner = comp.find(EuiLoadingSpinner);
expect(loadingSpinner).toHaveLength(1);
});
test('renders error state', () => {
jest.spyOn(hooks, 'useEsDocSearch').mockImplementation(() => [3, null, () => {}]);
const comp = mountWithIntl(
<ApmStacktrace
hit={{
raw: { _id: '1', _index: 'index1' },
flattened: {},
id: '',
}}
dataView={mockDataView}
/>
);
const errorComponent = comp.find('[data-test-subj="unifiedDocViewerApmStacktraceErrorMsg"]');
expect(errorComponent).toHaveLength(1);
});
test('renders log stacktrace', () => {
const mockHit = getMockHit({
id: '1',
grouping_key: '1',
log: {
message: 'Log message',
stacktrace: [
{
exclude_from_grouping: false,
abs_path: 'test.js',
filename: 'test.js',
line: {
number: 1,
context: 'console.log(err)',
},
function: '<anonymous>',
context: {
pre: ['console.log(err)'],
post: ['console.log(err)'],
},
vars: {},
},
{
exclude_from_grouping: false,
library_frame: true,
abs_path: 'test.js',
filename: 'test.js',
line: {
number: 1,
},
function: 'test',
vars: {},
},
],
},
});
jest.spyOn(hooks, 'useEsDocSearch').mockImplementation(() => [2, mockHit, () => {}]);
const comp = mountWithIntl(
<EuiThemeProvider>
<ApmStacktrace
hit={{
raw: { _id: '1', _index: 'index1' },
flattened: {},
id: '',
}}
dataView={mockDataView}
/>
</EuiThemeProvider>
);
const stacktraceComponent = comp.find(Stacktrace);
expect(stacktraceComponent).toHaveLength(1);
});
test('renders exception stacktrace', () => {
const mockHit = getMockHit({
id: '1',
grouping_key: '1',
exception: [
{
message: 'Exception stacktrace',
stacktrace: [
{
exclude_from_grouping: false,
abs_path: 'test.js',
filename: 'test.js',
line: {
number: 1,
context: 'console.log(err)',
},
function: '<anonymous>',
context: {
pre: ['console.log(err)'],
post: ['console.log(err);'],
},
vars: {},
},
{
exclude_from_grouping: false,
library_frame: true,
abs_path: 'test.js',
filename: 'test.js',
line: {
number: 1,
},
function: 'test',
vars: {},
},
],
},
{
handled: true,
module: 'module',
attributes: {
test: 'test',
},
message: 'message',
type: 'type',
},
],
});
jest.spyOn(hooks, 'useEsDocSearch').mockImplementation(() => [2, mockHit, () => {}]);
const comp = mountWithIntl(
<EuiThemeProvider>
<ApmStacktrace
hit={{
raw: { _id: '1', _index: 'index1' },
flattened: {},
id: '',
}}
dataView={mockDataView}
/>
</EuiThemeProvider>
);
const stacktraceComponent = comp.find(ExceptionStacktrace);
expect(stacktraceComponent).toHaveLength(1);
});
test('renders plain text stacktrace', () => {
const mockHit = getMockHit({
id: '1',
grouping_key: '1',
exception: [
{
handled: true,
message: 'message',
type: 'type',
},
],
stack_trace: 'test',
});
jest.spyOn(hooks, 'useEsDocSearch').mockImplementation(() => [2, mockHit, () => {}]);
const comp = mountWithIntl(
<EuiThemeProvider>
<ApmStacktrace
hit={{
raw: { _id: '1', _index: 'index1' },
flattened: {},
id: '',
}}
dataView={mockDataView}
/>
</EuiThemeProvider>
);
const stacktraceComponent = comp.find(PlaintextStacktrace);
expect(stacktraceComponent).toHaveLength(1);
});
});
function getMockHit(error: Record<string, unknown>) {
return buildDataTableRecord({
_index: '.ds-logs-apm.error-default-2024.12.31-000001',
_id: 'id123',
_score: 1,
_source: {
data_stream: {
type: 'logs',
dataset: 'apm.error',
namespace: 'default',
},
'@timestamp': '2024-12-31T00:00:00.000Z',
message: 'Log stacktrace',
error,
},
});
}

View file

@ -0,0 +1,79 @@
/*
* 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 { EuiLoadingSpinner } from '@elastic/eui';
import { DataTableRecord } from '@kbn/discover-utils';
import { i18n } from '@kbn/i18n';
import React, { useEffect, useState } from 'react';
import { ExceptionStacktrace, PlaintextStacktrace, Stacktrace } from '@kbn/event-stacktrace';
import type { APMError } from '@kbn/apm-types';
import type { DataView } from '@kbn/data-views-plugin/public';
import { ElasticRequestState } from '@kbn/unified-doc-viewer';
import { useEsDocSearch } from '../../../../hooks';
export const APM_ERROR_DATASTREAM_FIELDS = {
dataStreamType: 'logs',
dataStreamDataset: 'apm.error',
};
export function ApmStacktrace({ hit, dataView }: { hit: DataTableRecord; dataView: DataView }) {
const [apmErrorDoc, setApmErrorDoc] = useState<APMError>();
const [requestState, esHit] = useEsDocSearch({
id: hit.raw._id || '',
index: hit.raw._index,
dataView,
requestSource: true,
});
useEffect(() => {
if (requestState === ElasticRequestState.Found && esHit) {
setApmErrorDoc(esHit?.raw._source as unknown as APMError);
}
}, [requestState, esHit]);
if (requestState === ElasticRequestState.Loading) {
return <EuiLoadingSpinner size="m" />;
}
if (requestState === ElasticRequestState.Error || requestState === ElasticRequestState.NotFound) {
return (
<p data-test-subj="unifiedDocViewerApmStacktraceErrorMsg">
{i18n.translate('unifiedDocViewer.apmStacktrace.errorMessage', {
defaultMessage: 'Failed to load stacktrace',
})}
</p>
);
}
const codeLanguage = apmErrorDoc?.service?.language?.name;
const exceptions = apmErrorDoc?.error?.exception || [];
const logStackframes = apmErrorDoc?.error?.log?.stacktrace;
const isPlaintextException =
!!apmErrorDoc?.error?.stack_trace && exceptions.length === 1 && !exceptions[0].stacktrace;
if (apmErrorDoc?.error?.log?.message) {
return <Stacktrace stackframes={logStackframes} codeLanguage={codeLanguage} />;
}
if (apmErrorDoc?.error?.exception?.length) {
return isPlaintextException ? (
<PlaintextStacktrace
message={exceptions[0].message}
type={exceptions[0]?.type}
stacktrace={apmErrorDoc?.error.stack_trace}
codeLanguage={codeLanguage}
/>
) : (
<ExceptionStacktrace codeLanguage={codeLanguage} exceptions={exceptions} />
);
}
return null;
}

View file

@ -0,0 +1,43 @@
/*
* 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 { EuiCodeBlock } from '@elastic/eui';
import { DataTableRecord, fieldConstants, getFieldValue } from '@kbn/discover-utils';
import { i18n } from '@kbn/i18n';
import React from 'react';
import type { DataView } from '@kbn/data-views-plugin/public';
import { APM_ERROR_DATASTREAM_FIELDS, ApmStacktrace } from './apm_stacktrace';
export function StacktraceContent({ hit, dataView }: { hit: DataTableRecord; dataView: DataView }) {
const errorStackTrace = getFieldValue(hit, fieldConstants.ERROR_STACK_TRACE) as string;
const dataStreamTypeField = getFieldValue(hit, fieldConstants.DATASTREAM_TYPE_FIELD) as string;
const dataStreamDatasetField = getFieldValue(
hit,
fieldConstants.DATASTREAM_DATASET_FIELD
) as string;
if (
dataStreamTypeField === APM_ERROR_DATASTREAM_FIELDS.dataStreamType &&
dataStreamDatasetField === APM_ERROR_DATASTREAM_FIELDS.dataStreamDataset
) {
return <ApmStacktrace hit={hit} dataView={dataView} />;
}
if (errorStackTrace) {
return <EuiCodeBlock isCopyable={true}>{errorStackTrace}</EuiCodeBlock>;
}
return (
<p>
{i18n.translate('unifiedDocViewer.stacktraceSection.errorMessage', {
defaultMessage: 'Failed to load stacktrace',
})}
</p>
);
}

View file

@ -37,7 +37,10 @@
"@kbn/router-utils",
"@kbn/unified-field-list",
"@kbn/core-lifecycle-browser",
"@kbn/management-settings-ids"
"@kbn/management-settings-ids",
"@kbn/apm-types",
"@kbn/event-stacktrace"
],
"exclude": [
"target/**/*",

View file

@ -90,8 +90,8 @@
"@kbn/apm-synthtrace/*": ["packages/kbn-apm-synthtrace/*"],
"@kbn/apm-synthtrace-client": ["packages/kbn-apm-synthtrace-client"],
"@kbn/apm-synthtrace-client/*": ["packages/kbn-apm-synthtrace-client/*"],
"@kbn/apm-types": ["x-pack/solutions/observability/packages/kbn-apm-types"],
"@kbn/apm-types/*": ["x-pack/solutions/observability/packages/kbn-apm-types/*"],
"@kbn/apm-types": ["x-pack/platform/packages/shared/kbn-apm-types"],
"@kbn/apm-types/*": ["x-pack/platform/packages/shared/kbn-apm-types/*"],
"@kbn/apm-utils": ["src/platform/packages/shared/kbn-apm-utils"],
"@kbn/apm-utils/*": ["src/platform/packages/shared/kbn-apm-utils/*"],
"@kbn/app-link-test-plugin": ["test/plugin_functional/plugins/app_link_test"],
@ -898,6 +898,8 @@
"@kbn/event-log-fixture-plugin/*": ["x-pack/test/plugin_api_integration/plugins/event_log/*"],
"@kbn/event-log-plugin": ["x-pack/platform/plugins/shared/event_log"],
"@kbn/event-log-plugin/*": ["x-pack/platform/plugins/shared/event_log/*"],
"@kbn/event-stacktrace": ["x-pack/platform/packages/shared/kbn-event-stacktrace"],
"@kbn/event-stacktrace/*": ["x-pack/platform/packages/shared/kbn-event-stacktrace/*"],
"@kbn/expandable-flyout": ["x-pack/solutions/security/packages/expandable-flyout"],
"@kbn/expandable-flyout/*": ["x-pack/solutions/security/packages/expandable-flyout/*"],
"@kbn/expect": ["packages/kbn-expect"],
@ -1124,6 +1126,8 @@
"@kbn/kbn-tp-custom-visualizations-plugin/*": ["test/plugin_functional/plugins/kbn_tp_custom_visualizations/*"],
"@kbn/kbn-tp-run-pipeline-plugin": ["test/interpreter_functional/plugins/kbn_tp_run_pipeline"],
"@kbn/kbn-tp-run-pipeline-plugin/*": ["test/interpreter_functional/plugins/kbn_tp_run_pipeline/*"],
"@kbn/key-value-metadata-table": ["x-pack/platform/packages/shared/kbn-key-value-metadata-table"],
"@kbn/key-value-metadata-table/*": ["x-pack/platform/packages/shared/kbn-key-value-metadata-table/*"],
"@kbn/kibana-cors-test-plugin": ["x-pack/test/functional_cors/plugins/kibana_cors_test"],
"@kbn/kibana-cors-test-plugin/*": ["x-pack/test/functional_cors/plugins/kibana_cors_test/*"],
"@kbn/kibana-manifest-schema": ["packages/kbn-kibana-manifest-schema"],

View file

@ -163,7 +163,8 @@
"xpack.synthetics": ["solutions/observability/plugins/synthetics"],
"xpack.ux": ["solutions/observability/plugins/ux"],
"xpack.urlDrilldown": "platform/plugins/private/drilldowns/url_drilldown",
"xpack.watcher": "platform/plugins/private/watcher"
"xpack.watcher": "platform/plugins/private/watcher",
"xpack.eventStacktrace": "platform/packages/shared/kbn-event-stacktrace"
},
"exclude": ["examples"],
"translations": [

View file

@ -4,6 +4,6 @@
"owner": [
"@elastic/obs-ux-infra_services-team"
],
"group": "observability",
"visibility": "private"
}
"group": "platform",
"visibility": "shared"
}

View file

@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { setGlobalConfig } from '@storybook/testing-react';
import * as globalStorybookConfig from './preview';
setGlobalConfig(globalStorybookConfig);

View file

@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
module.exports = require('@kbn/storybook').defaultConfig;

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EuiThemeProviderDecorator } from '@kbn/kibana-react-plugin/common';
export const decorators = [EuiThemeProviderDecorator];

View file

@ -0,0 +1,19 @@
# @kbn/event-stacktrace
This package contains components that render event (error, log, span) stack traces.
## Unit Tests (Jest)
```
node scripts/jest --config x-pack/platform/packages/shared/kbn-event-stacktrace/README.md [--watch]
```
## Storybook
### Start
```
yarn storybook event_stacktrace
```
All files with a .stories.tsx extension will be loaded. You can access the development environment at http://localhost:9001.

View file

@ -0,0 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './src/components/stacktrace/cause_stacktrace';
export * from './src/components/stacktrace/frame_heading';
export * from './src/components/stacktrace';
export * from './src/components/stacktrace/library_stacktrace';
export * from './src/components/stacktrace/stackframe';
export * from './src/components/stacktrace/variables';
export * from './src/components/stacktrace/plain/plaintext_stacktrace';
export * from './src/components/stacktrace/exception/exception_stacktrace';

View file

@ -0,0 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../../../..',
roots: ['<rootDir>/x-pack/platform/packages/shared/kbn-event-stacktrace'],
setupFiles: [
'<rootDir>/x-pack/platform/packages/shared/kbn-event-stacktrace/.storybook/jest_setup.js',
],
};

View file

@ -0,0 +1,10 @@
{
"type": "shared-browser",
"id": "@kbn/event-stacktrace",
"owner": [
"@elastic/obs-ux-infra_services-team",
"@elastic/obs-ux-logs-team"
],
"group": "platform",
"visibility": "shared"
}

View file

@ -0,0 +1,6 @@
{
"name": "@kbn/event-stacktrace",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0"
}

View file

@ -7,7 +7,7 @@
import { shallow } from 'enzyme';
import React from 'react';
import { mountWithTheme } from '../../../utils/test_helpers';
import { mountWithTheme } from '../../utils/test_helpers';
import { CauseStacktrace } from './cause_stacktrace';
describe('CauseStacktrace', () => {

View file

@ -9,8 +9,8 @@ import { EuiAccordion, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import { Stackframe } from '@kbn/apm-types';
import { Stacktrace } from '.';
import type { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe';
const Accordion = euiStyled(EuiAccordion)`
border-top: ${({ theme }) => theme.eui.euiBorderThin};
@ -37,7 +37,7 @@ function CausedBy({ message }: { message: string }) {
return (
<CausedByContainer>
<CausedByHeading>
{i18n.translate('xpack.apm.stacktraceTab.causedByFramesToogleButtonLabel', {
{i18n.translate('xpack.eventStacktrace.stacktraceTab.causedByFramesToogleButtonLabel', {
defaultMessage: 'Caused By',
})}
</CausedByHeading>

View file

@ -7,7 +7,7 @@
import React from 'react';
import { EuiCodeBlock } from '@elastic/eui';
import type { StackframeWithLineContext } from '../../../../typings/es_schemas/raw/fields/stackframe';
import type { StackframeWithLineContext } from '@kbn/apm-types';
function getStackframeLines(stackframe: StackframeWithLineContext) {
const line = stackframe.line.context;

View file

@ -6,10 +6,10 @@
*/
import React from 'react';
import type { Exception } from '../../../../../typings/es_schemas/raw/error_raw';
import { Stacktrace } from '../../../shared/stacktrace';
import { CauseStacktrace } from '../../../shared/stacktrace/cause_stacktrace';
import type { Exception } from '@kbn/apm-types/es_schemas_raw';
import { ExceptionStacktraceTitle } from './exception_stacktrace_title';
import { CauseStacktrace } from '../cause_stacktrace';
import { Stacktrace } from '..';
interface ExceptionStacktraceProps {
codeLanguage?: string;

View file

@ -6,8 +6,8 @@
*/
import React from 'react';
import type { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe';
import { renderWithTheme } from '../../../utils/test_helpers';
import { Stackframe } from '@kbn/apm-types';
import { renderWithTheme } from '../../utils/test_helpers';
import { FrameHeading } from './frame_heading';
function getRenderedStackframeText(stackframe: Stackframe, codeLanguage: string, idx: string) {

View file

@ -8,8 +8,7 @@
import type { ComponentType } from 'react';
import React from 'react';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import type { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe';
import type { FrameHeadingRendererProps } from './frame_heading_renderers';
import { Stackframe } from '@kbn/apm-types';
import {
CSharpFrameHeadingRenderer,
DefaultFrameHeadingRenderer,
@ -17,6 +16,7 @@ import {
JavaScriptFrameHeadingRenderer,
RubyFrameHeadingRenderer,
PhpFrameHeadingRenderer,
FrameHeadingRendererProps,
} from './frame_heading_renderers';
const FileDetails = euiStyled.div`

View file

@ -5,8 +5,8 @@
* 2.0.
*/
import type { ComponentType } from 'react';
import type { Stackframe } from '../../../../../typings/es_schemas/raw/fields/stackframe';
import { ComponentType } from 'react';
import type { Stackframe } from '@kbn/apm-types';
export interface FrameHeadingRendererProps {
fileDetailComponent: ComponentType<React.PropsWithChildren<{}>>;

View file

@ -8,9 +8,8 @@
import { i18n } from '@kbn/i18n';
import { isEmpty, last } from 'lodash';
import React, { Fragment } from 'react';
import { EuiCodeBlock } from '@elastic/eui';
import type { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe';
import { EmptyMessage } from '../empty_message';
import { EuiCodeBlock, EuiEmptyPrompt } from '@elastic/eui';
import type { Stackframe } from '@kbn/apm-types';
import { LibraryStacktrace } from './library_stacktrace';
import { Stackframe as StackframeComponent } from './stackframe';
@ -23,11 +22,15 @@ interface Props {
export function Stacktrace({ stackframes = [], codeLanguage }: Props) {
if (isEmpty(stackframes)) {
return (
<EmptyMessage
heading={i18n.translate('xpack.apm.stacktraceTab.noStacktraceAvailableLabel', {
defaultMessage: 'No stack trace available.',
})}
hideSubheading
<EuiEmptyPrompt
titleSize="s"
title={
<div>
{i18n.translate('xpack.eventStacktrace.stacktraceTab.noStacktraceAvailableLabel', {
defaultMessage: 'No stack trace available.',
})}
</div>
}
/>
);
}

View file

@ -6,7 +6,7 @@
*/
import React from 'react';
import { renderWithTheme } from '../../../utils/test_helpers';
import { renderWithTheme } from '../../utils/test_helpers';
import { LibraryStacktrace } from './library_stacktrace';
describe('LibraryStacktrace', () => {

View file

@ -9,7 +9,7 @@ import { EuiAccordion } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import type { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe';
import { Stackframe } from '@kbn/apm-types';
import { Stackframe as StackframeComponent } from './stackframe';
const LibraryStacktraceAccordion = euiStyled(EuiAccordion)`
@ -29,10 +29,13 @@ export function LibraryStacktrace({ codeLanguage, id, stackframes }: Props) {
return (
<LibraryStacktraceAccordion
buttonContent={i18n.translate('xpack.apm.stacktraceTab.libraryFramesToogleButtonLabel', {
defaultMessage: '{count, plural, one {# library frame} other {# library frames}}',
values: { count: stackframes.length },
})}
buttonContent={i18n.translate(
'xpack.eventStacktrace.stacktraceTab.libraryFramesToogleButtonLabel',
{
defaultMessage: '{count, plural, one {# library frame} other {# library frames}}',
values: { count: stackframes.length },
}
)}
data-test-subj="LibraryStacktraceAccordion"
id={id}
>

View file

@ -7,7 +7,7 @@
import React from 'react';
import { EuiCodeBlock } from '@elastic/eui';
import { ExceptionStacktraceTitle } from './exception_stacktrace_title';
import { ExceptionStacktraceTitle } from '../exception/exception_stacktrace_title';
interface PlaintextStacktraceProps {
codeLanguage?: string;

View file

@ -6,10 +6,9 @@
*/
import React from 'react';
import type { ReactWrapper } from 'enzyme';
import { shallow } from 'enzyme';
import type { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe';
import { mountWithTheme } from '../../../utils/test_helpers';
import { ReactWrapper, shallow } from 'enzyme';
import type { Stackframe } from '@kbn/apm-types';
import { mountWithTheme } from '../../utils/test_helpers';
import { Stackframe as StackframeComponent } from './stackframe';
import stacktracesMock from './__fixtures__/stacktraces.json';

View file

@ -8,10 +8,7 @@
import { EuiAccordion } from '@elastic/eui';
import React from 'react';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import type {
Stackframe as StackframeType,
StackframeWithLineContext,
} from '../../../../typings/es_schemas/raw/fields/stackframe';
import type { Stackframe as StackframeType, StackframeWithLineContext } from '@kbn/apm-types';
import { Context } from './context';
import { FrameHeading } from './frame_heading';
import { Variables } from './variables';

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import type { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe';
import { Stackframe } from '@kbn/apm-types';
import { getGroupedStackframes } from '.';
import stacktracesMock from './__fixtures__/stacktraces.json';

View file

@ -8,10 +8,9 @@
import { EuiAccordion } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { KeyValueTable, getFlattenedKeyValuePairs } from '@kbn/key-value-metadata-table';
import { Stackframe } from '@kbn/apm-types';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import type { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe';
import { KeyValueTable } from '../key_value_table';
import { flattenObject } from '../../../../common/utils/flatten_object';
const VariablesContainer = euiStyled.div`
background: ${({ theme }) => theme.eui.euiColorEmptyShade};
@ -28,16 +27,19 @@ export function Variables({ vars }: Props) {
if (!vars) {
return null;
}
const flattenedVariables = flattenObject(vars);
const flattenedVariables = getFlattenedKeyValuePairs(vars);
return (
<React.Fragment>
<VariablesContainer>
<EuiAccordion
id="local-variables"
className="euiAccordion"
buttonContent={i18n.translate('xpack.apm.stacktraceTab.localVariablesToogleButtonLabel', {
defaultMessage: 'Local variables',
})}
buttonContent={i18n.translate(
'xpack.eventStacktrace.stacktraceTab.localVariablesToogleButtonLabel',
{
defaultMessage: 'Local variables',
}
)}
>
<React.Fragment>
<KeyValueTable keyValuePairs={flattenedVariables} />

View file

@ -0,0 +1,27 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
// eslint-disable-next-line import/no-extraneous-dependencies
import { render } from '@testing-library/react';
// eslint-disable-next-line import/no-extraneous-dependencies
import { mount, MountRendererProps } from 'enzyme';
export function renderWithTheme(component: React.ReactNode, params?: any) {
return render(<EuiThemeProvider>{component}</EuiThemeProvider>, params);
}
export function mountWithTheme(tree: React.ReactElement<any>) {
function WrappingThemeProvider(props: any) {
return <EuiThemeProvider>{props.children}</EuiThemeProvider>;
}
return mount(tree, {
wrappingComponent: WrappingThemeProvider,
} as MountRendererProps);
}

View file

@ -0,0 +1,26 @@
{
"extends": "../../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node",
"react",
"@testing-library/jest-dom"
]
},
"include": [
"**/*.ts",
"**/*.tsx",
"src/**/*.json"
],
"exclude": [
"target/**/*"
],
"kbn_references": [
"@kbn/i18n",
"@kbn/apm-types",
"@kbn/key-value-metadata-table",
"@kbn/kibana-react-plugin"
]
}

View file

@ -0,0 +1,3 @@
# @kbn/key-value-metadata-table
Key-value metadata table

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export { KeyValueTable } from './src';
export { getFlattenedKeyValuePairs } from './src/utils/get_flattened_key_value_pairs';
export type { KeyValuePair } from './src/utils/get_flattened_key_value_pairs';

View file

@ -0,0 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../../../..',
roots: ['<rootDir>/x-pack/platform/packages/shared/kbn-key-value-metadata-table'],
};

View file

@ -0,0 +1,12 @@
{
"type": "shared-common",
"id": "@kbn/key-value-metadata-table",
"owner": [
"@elastic/obs-ux-infra_services-team",
"@elastic/obs-ux-logs-team"
],
"group": "platform",
"visibility": "shared"
}

View file

@ -0,0 +1,6 @@
{
"name": "@kbn/key-value-metadata-table",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0"
}

View file

@ -8,7 +8,7 @@
import { isBoolean, isNumber, isObject } from 'lodash';
import React from 'react';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n';
import { i18n } from '@kbn/i18n';
const EmptyValue = euiStyled.span`
color: ${({ theme }) => theme.eui.euiColorMediumShade};
@ -29,7 +29,13 @@ export function FormattedValue({ value }: { value: any }): JSX.Element {
} else if (isBoolean(value) || isNumber(value)) {
return <React.Fragment>{String(value)}</React.Fragment>;
} else if (!value) {
return <EmptyValue>{NOT_AVAILABLE_LABEL}</EmptyValue>;
return (
<EmptyValue>
{i18n.translate('keyValueMetadataTable.notAvailableLabel', {
defaultMessage: 'N/A',
})}
</EmptyValue>
);
}
return <React.Fragment>{value}</React.Fragment>;

Some files were not shown because too many files have changed in this diff Show more