diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.stories.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.stories.tsx new file mode 100644 index 0000000000000..5ad6fd547169d --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.stories.tsx @@ -0,0 +1,807 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { EuiThemeProvider } from '../../../../../../observability/public'; +import { Exception } from '../../../../../typings/es_schemas/raw/error_raw'; +import { ExceptionStacktrace } from './ExceptionStacktrace'; + +storiesOf('app/ErrorGroupDetails/DetailView/ExceptionStacktrace', module) + .addDecorator((storyFn) => { + return {storyFn()}; + }) + .add('JavaScript with some context', () => { + const exceptions: Exception[] = [ + { + code: '503', + stacktrace: [ + { + library_frame: true, + exclude_from_grouping: false, + filename: 'node_modules/elastic-apm-http-client/index.js', + abs_path: '/app/node_modules/elastic-apm-http-client/index.js', + line: { + number: 711, + context: + " const err = new Error('Unexpected APM Server response when polling config')", + }, + function: 'processConfigErrorResponse', + context: { + pre: ['', 'function processConfigErrorResponse (res, buf) {'], + post: ['', ' err.code = res.statusCode'], + }, + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'node_modules/elastic-apm-http-client/index.js', + abs_path: '/app/node_modules/elastic-apm-http-client/index.js', + line: { + number: 196, + context: + ' res.destroy(processConfigErrorResponse(res, buf))', + }, + function: '', + context: { + pre: [' }', ' } else {'], + post: [' }', ' })'], + }, + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'node_modules/fast-stream-to-buffer/index.js', + abs_path: '/app/node_modules/fast-stream-to-buffer/index.js', + line: { + number: 20, + context: ' cb(err, buffers[0], stream)', + }, + function: 'IncomingMessage.', + context: { + pre: [' break', ' case 1:'], + post: [' break', ' default:'], + }, + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'node_modules/once/once.js', + abs_path: '/app/node_modules/once/once.js', + line: { + number: 25, + context: ' return f.value = fn.apply(this, arguments)', + }, + function: 'f', + context: { + pre: [' if (f.called) return f.value', ' f.called = true'], + post: [' }', ' f.called = false'], + }, + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'node_modules/end-of-stream/index.js', + abs_path: '/app/node_modules/end-of-stream/index.js', + line: { + number: 36, + context: '\t\tif (!writable) callback.call(stream);', + }, + function: 'onend', + context: { + pre: ['\tvar onend = function() {', '\t\treadable = false;'], + post: ['\t};', ''], + }, + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: 'events.js', + filename: 'events.js', + line: { + number: 327, + }, + function: 'emit', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: '_stream_readable.js', + abs_path: '_stream_readable.js', + line: { + number: 1220, + }, + function: 'endReadableNT', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'internal/process/task_queues.js', + abs_path: 'internal/process/task_queues.js', + line: { + number: 84, + }, + function: 'processTicksAndRejections', + }, + ], + module: 'elastic-apm-http-client', + handled: false, + attributes: { + response: + '\r\n503 Service Temporarily Unavailable\r\n\r\n

503 Service Temporarily Unavailable

\r\n
nginx/1.17.7
\r\n\r\n\r\n', + }, + type: 'Error', + message: 'Unexpected APM Server response when polling config', + }, + ]; + + return ( + + ); + }) + .add('Ruby with context and library frames', () => { + const exceptions: Exception[] = [ + { + stacktrace: [ + { + library_frame: true, + exclude_from_grouping: false, + filename: 'active_record/core.rb', + abs_path: + '/usr/local/bundle/gems/activerecord-5.2.4.1/lib/active_record/core.rb', + line: { + number: 177, + }, + function: 'find', + }, + { + library_frame: false, + exclude_from_grouping: false, + filename: 'api/orders_controller.rb', + abs_path: '/app/app/controllers/api/orders_controller.rb', + line: { + number: 23, + context: ' render json: Order.find(params[:id])\n', + }, + function: 'show', + context: { + pre: ['\n', ' def show\n'], + post: [' end\n', ' end\n'], + }, + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_controller/metal/basic_implicit_render.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_controller/metal/basic_implicit_render.rb', + line: { + number: 6, + }, + function: 'send_action', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'abstract_controller/base.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/abstract_controller/base.rb', + line: { + number: 194, + }, + function: 'process_action', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_controller/metal/rendering.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_controller/metal/rendering.rb', + line: { + number: 30, + }, + function: 'process_action', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'abstract_controller/callbacks.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/abstract_controller/callbacks.rb', + line: { + number: 42, + }, + function: 'block in process_action', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'active_support/callbacks.rb', + abs_path: + '/usr/local/bundle/gems/activesupport-5.2.4.1/lib/active_support/callbacks.rb', + line: { + number: 132, + }, + function: 'run_callbacks', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'abstract_controller/callbacks.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/abstract_controller/callbacks.rb', + line: { + number: 41, + }, + function: 'process_action', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_controller/metal/rescue.rb', + filename: 'action_controller/metal/rescue.rb', + line: { + number: 22, + }, + function: 'process_action', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_controller/metal/instrumentation.rb', + filename: 'action_controller/metal/instrumentation.rb', + line: { + number: 34, + }, + function: 'block in process_action', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'active_support/notifications.rb', + abs_path: + '/usr/local/bundle/gems/activesupport-5.2.4.1/lib/active_support/notifications.rb', + line: { + number: 168, + }, + function: 'block in instrument', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'active_support/notifications/instrumenter.rb', + abs_path: + '/usr/local/bundle/gems/activesupport-5.2.4.1/lib/active_support/notifications/instrumenter.rb', + line: { + number: 23, + }, + function: 'instrument', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'active_support/notifications.rb', + abs_path: + '/usr/local/bundle/gems/activesupport-5.2.4.1/lib/active_support/notifications.rb', + line: { + number: 168, + }, + function: 'instrument', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_controller/metal/instrumentation.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_controller/metal/instrumentation.rb', + line: { + number: 32, + }, + function: 'process_action', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_controller/metal/params_wrapper.rb', + filename: 'action_controller/metal/params_wrapper.rb', + line: { + number: 256, + }, + function: 'process_action', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'active_record/railties/controller_runtime.rb', + abs_path: + '/usr/local/bundle/gems/activerecord-5.2.4.1/lib/active_record/railties/controller_runtime.rb', + line: { + number: 24, + }, + function: 'process_action', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'abstract_controller/base.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/abstract_controller/base.rb', + line: { + number: 134, + }, + function: 'process', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_view/rendering.rb', + abs_path: + '/usr/local/bundle/gems/actionview-5.2.4.1/lib/action_view/rendering.rb', + line: { + number: 32, + }, + function: 'process', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_controller/metal.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_controller/metal.rb', + line: { + number: 191, + }, + function: 'dispatch', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_controller/metal.rb', + filename: 'action_controller/metal.rb', + line: { + number: 252, + }, + function: 'dispatch', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'action_dispatch/routing/route_set.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/routing/route_set.rb', + line: { + number: 52, + }, + function: 'dispatch', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/routing/route_set.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/routing/route_set.rb', + line: { + number: 34, + }, + function: 'serve', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/journey/router.rb', + filename: 'action_dispatch/journey/router.rb', + line: { + number: 52, + }, + function: 'block in serve', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/journey/router.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/journey/router.rb', + line: { + number: 35, + }, + function: 'each', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/journey/router.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/journey/router.rb', + line: { + number: 35, + }, + function: 'serve', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/routing/route_set.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/routing/route_set.rb', + line: { + number: 840, + }, + function: 'call', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'rack/static.rb', + abs_path: '/usr/local/bundle/gems/rack-2.2.3/lib/rack/static.rb', + line: { + number: 161, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'rack/tempfile_reaper.rb', + abs_path: + '/usr/local/bundle/gems/rack-2.2.3/lib/rack/tempfile_reaper.rb', + line: { + number: 15, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'rack/etag.rb', + abs_path: '/usr/local/bundle/gems/rack-2.2.3/lib/rack/etag.rb', + line: { + number: 27, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'rack/conditional_get.rb', + abs_path: + '/usr/local/bundle/gems/rack-2.2.3/lib/rack/conditional_get.rb', + line: { + number: 27, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'rack/head.rb', + abs_path: '/usr/local/bundle/gems/rack-2.2.3/lib/rack/head.rb', + line: { + number: 12, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/http/content_security_policy.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/http/content_security_policy.rb', + line: { + number: 18, + }, + function: 'call', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'rack/session/abstract/id.rb', + abs_path: + '/usr/local/bundle/gems/rack-2.2.3/lib/rack/session/abstract/id.rb', + line: { + number: 266, + }, + function: 'context', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'rack/session/abstract/id.rb', + abs_path: + '/usr/local/bundle/gems/rack-2.2.3/lib/rack/session/abstract/id.rb', + line: { + number: 260, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/middleware/cookies.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/middleware/cookies.rb', + line: { + number: 670, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/middleware/callbacks.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/middleware/callbacks.rb', + line: { + number: 28, + }, + function: 'block in call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'active_support/callbacks.rb', + abs_path: + '/usr/local/bundle/gems/activesupport-5.2.4.1/lib/active_support/callbacks.rb', + line: { + number: 98, + }, + function: 'run_callbacks', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/middleware/callbacks.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/middleware/callbacks.rb', + line: { + number: 26, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/middleware/debug_exceptions.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/middleware/debug_exceptions.rb', + line: { + number: 61, + }, + function: 'call', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'action_dispatch/middleware/show_exceptions.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/middleware/show_exceptions.rb', + line: { + number: 33, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'lograge/rails_ext/rack/logger.rb', + abs_path: + '/usr/local/bundle/gems/lograge-0.11.2/lib/lograge/rails_ext/rack/logger.rb', + line: { + number: 15, + }, + function: 'call_app', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'rails/rack/logger.rb', + abs_path: + '/usr/local/bundle/gems/railties-5.2.4.1/lib/rails/rack/logger.rb', + line: { + number: 28, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/middleware/remote_ip.rb', + filename: 'action_dispatch/middleware/remote_ip.rb', + line: { + number: 81, + }, + function: 'call', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'request_store/middleware.rb', + abs_path: + '/usr/local/bundle/gems/request_store-1.5.0/lib/request_store/middleware.rb', + line: { + number: 19, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/middleware/request_id.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/middleware/request_id.rb', + line: { + number: 27, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'rack/method_override.rb', + abs_path: + '/usr/local/bundle/gems/rack-2.2.3/lib/rack/method_override.rb', + line: { + number: 24, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'rack/runtime.rb', + abs_path: '/usr/local/bundle/gems/rack-2.2.3/lib/rack/runtime.rb', + line: { + number: 22, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'active_support/cache/strategy/local_cache_middleware.rb', + abs_path: + '/usr/local/bundle/gems/activesupport-5.2.4.1/lib/active_support/cache/strategy/local_cache_middleware.rb', + line: { + number: 29, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/middleware/executor.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/middleware/executor.rb', + line: { + number: 14, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/middleware/static.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/middleware/static.rb', + line: { + number: 127, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'rack/sendfile.rb', + abs_path: '/usr/local/bundle/gems/rack-2.2.3/lib/rack/sendfile.rb', + line: { + number: 110, + }, + function: 'call', + }, + { + library_frame: false, + exclude_from_grouping: false, + filename: 'opbeans_shuffle.rb', + abs_path: '/app/lib/opbeans_shuffle.rb', + line: { + number: 32, + context: ' @app.call(env)\n', + }, + function: 'call', + context: { + pre: [' end\n', ' else\n'], + post: [' end\n', ' rescue Timeout::Error\n'], + }, + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'elastic_apm/middleware.rb', + abs_path: + '/usr/local/bundle/gems/elastic-apm-3.8.0/lib/elastic_apm/middleware.rb', + line: { + number: 36, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'rails/engine.rb', + abs_path: + '/usr/local/bundle/gems/railties-5.2.4.1/lib/rails/engine.rb', + line: { + number: 524, + }, + function: 'call', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'puma/configuration.rb', + abs_path: + '/usr/local/bundle/gems/puma-4.3.5/lib/puma/configuration.rb', + line: { + number: 228, + }, + function: 'call', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'puma/server.rb', + abs_path: '/usr/local/bundle/gems/puma-4.3.5/lib/puma/server.rb', + line: { + number: 713, + }, + function: 'handle_request', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'puma/server.rb', + abs_path: '/usr/local/bundle/gems/puma-4.3.5/lib/puma/server.rb', + line: { + number: 472, + }, + function: 'process_client', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'puma/server.rb', + abs_path: '/usr/local/bundle/gems/puma-4.3.5/lib/puma/server.rb', + line: { + number: 328, + }, + function: 'block in run', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'puma/thread_pool.rb', + abs_path: + '/usr/local/bundle/gems/puma-4.3.5/lib/puma/thread_pool.rb', + line: { + number: 134, + }, + function: 'block in spawn_thread', + }, + ], + handled: false, + module: 'ActiveRecord', + message: "Couldn't find Order with 'id'=956", + type: 'ActiveRecord::RecordNotFound', + }, + ]; + + return ; + }); diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.tsx index c70467c6d031f..977bf2dd09cc0 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { EuiAccordion, EuiTitle } from '@elastic/eui'; import { px, unit } from '../../../style/variables'; import { Stacktrace } from '.'; -import { IStackframe } from '../../../../typings/es_schemas/raw/fields/stackframe'; +import { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe'; // @ts-ignore Styled Components has trouble inferring the types of the default props here. const Accordion = styled(EuiAccordion)` @@ -55,7 +55,7 @@ interface CauseStacktraceProps { codeLanguage?: string; id: string; message?: string; - stackframes?: IStackframe[]; + stackframes?: Stackframe[]; } export function CauseStacktrace({ diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/Context.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/Context.tsx index edada6012d2e2..72fdc93de9545 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/Context.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/Context.tsx @@ -21,7 +21,7 @@ import { registerLanguage } from 'react-syntax-highlighter/dist/light'; // @ts-ignore import { xcode } from 'react-syntax-highlighter/dist/styles'; import styled from 'styled-components'; -import { IStackframeWithLineContext } from '../../../../typings/es_schemas/raw/fields/stackframe'; +import { StackframeWithLineContext } from '../../../../typings/es_schemas/raw/fields/stackframe'; import { borderRadius, px, unit, units } from '../../../style/variables'; registerLanguage('javascript', javascript); @@ -102,20 +102,20 @@ const Code = styled.code` z-index: 2; `; -function getStackframeLines(stackframe: IStackframeWithLineContext) { +function getStackframeLines(stackframe: StackframeWithLineContext) { const line = stackframe.line.context; const preLines = stackframe.context?.pre || []; const postLines = stackframe.context?.post || []; return [...preLines, line, ...postLines]; } -function getStartLineNumber(stackframe: IStackframeWithLineContext) { +function getStartLineNumber(stackframe: StackframeWithLineContext) { const preLines = size(stackframe.context?.pre || []); return stackframe.line.number - preLines; } interface Props { - stackframe: IStackframeWithLineContext; + stackframe: StackframeWithLineContext; codeLanguage?: string; isLibraryFrame: boolean; } diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/FrameHeading.test.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/FrameHeading.test.tsx new file mode 100644 index 0000000000000..908399c414cf5 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/FrameHeading.test.tsx @@ -0,0 +1,252 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe'; +import { renderWithTheme } from '../../../utils/testHelpers'; +import { FrameHeading } from './FrameHeading'; + +function getRenderedStackframeText( + stackframe: Stackframe, + codeLanguage: string +) { + const result = renderWithTheme( + + ); + + return result.getByTestId('FrameHeading').textContent; +} + +describe('FrameHeading', () => { + describe('with a Go stackframe', () => { + it('renders', () => { + expect( + getRenderedStackframeText( + { + exclude_from_grouping: false, + filename: 'main.go', + abs_path: '/src/opbeans-go/main.go', + line: { number: 196 }, + function: 'Main.func2', + module: 'main', + }, + 'go' + ) + ).toEqual('main.go in Main.func2 at line 196'); + }); + }); + + describe('with a Java stackframe', () => { + it('renders', () => { + expect( + getRenderedStackframeText( + { + library_frame: true, + exclude_from_grouping: false, + filename: 'OutputBuffer.java', + classname: 'org.apache.catalina.connector.OutputBuffer', + line: { number: 825 }, + module: 'org.apache.catalina.connector', + function: 'flushByteBuffer', + }, + 'Java' + ) + ).toEqual( + 'at org.apache.catalina.connector.OutputBuffer.flushByteBuffer(OutputBuffer.java:825)' + ); + }); + }); + + describe('with a .NET stackframe', () => { + describe('with a classname', () => { + it('renders', () => { + expect( + getRenderedStackframeText( + { + classname: 'OpbeansDotnet.Controllers.CustomersController', + exclude_from_grouping: false, + filename: + '/src/opbeans-dotnet/Controllers/CustomersController.cs', + abs_path: + '/src/opbeans-dotnet/Controllers/CustomersController.cs', + line: { number: 23 }, + module: + 'opbeans-dotnet, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null', + function: 'Get', + }, + 'C#' + ) + ).toEqual( + 'OpbeansDotnet.Controllers.CustomersController in Get in /src/opbeans-dotnet/Controllers/CustomersController.cs at line 23' + ); + }); + }); + + describe('with no classname', () => { + it('renders', () => { + expect( + getRenderedStackframeText( + { + exclude_from_grouping: false, + filename: + 'Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider+ResultEnumerable`1', + line: { number: 0 }, + function: 'GetEnumerator', + module: + 'Microsoft.EntityFrameworkCore, Version=2.2.6.0, Culture=neutral, PublicKeyToken=adb9793829ddae60', + }, + 'C#' + ) + ).toEqual( + 'Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider+ResultEnumerable`1 in GetEnumerator' + ); + }); + }); + }); + + describe('with a Node stackframe', () => { + it('renders', () => { + expect( + getRenderedStackframeText( + { + library_frame: true, + exclude_from_grouping: false, + filename: 'internal/async_hooks.js', + abs_path: 'internal/async_hooks.js', + line: { number: 120 }, + function: 'callbackTrampoline', + }, + 'javascript' + ) + ).toEqual('at callbackTrampoline (internal/async_hooks.js:120)'); + }); + + describe('with a classname', () => { + it('renders', () => { + expect( + getRenderedStackframeText( + { + classname: 'TCPConnectWrap', + exclude_from_grouping: false, + library_frame: true, + filename: 'internal/stream_base_commons.js', + abs_path: 'internal/stream_base_commons.js', + line: { number: 205 }, + function: 'onStreamRead', + }, + 'javascript' + ) + ).toEqual( + 'at TCPConnectWrap.onStreamRead (internal/stream_base_commons.js:205)' + ); + }); + }); + + describe('with no classname and no function', () => { + it('renders', () => { + expect( + getRenderedStackframeText( + { + exclude_from_grouping: false, + library_frame: true, + filename: 'internal/stream_base_commons.js', + abs_path: 'internal/stream_base_commons.js', + line: { number: 205 }, + }, + 'javascript' + ) + ).toEqual('at (internal/stream_base_commons.js:205)'); + }); + }); + }); + + describe('with a Python stackframe', () => { + it('renders', () => { + expect( + getRenderedStackframeText( + { + exclude_from_grouping: false, + library_frame: false, + filename: 'opbeans/views.py', + abs_path: '/app/opbeans/views.py', + line: { + number: 190, + context: ' return post_order(request)', + }, + module: 'opbeans.views', + function: 'orders', + context: { + pre: [ + ' # set transaction name to post_order', + " elasticapm.set_transaction_name('POST opbeans.views.post_order')", + ], + post: [ + ' order_list = list(m.Order.objects.values(', + " 'id', 'customer_id', 'customer__full_name', 'created_at'", + ], + }, + vars: { request: "" }, + }, + 'python' + ) + ).toEqual('opbeans/views.py in orders at line 190'); + }); + }); + + describe('with a Ruby stackframe', () => { + it('renders', () => { + expect( + getRenderedStackframeText( + { + library_frame: false, + exclude_from_grouping: false, + abs_path: '/app/app/controllers/api/customers_controller.rb', + filename: 'api/customers_controller.rb', + line: { + number: 15, + context: ' render json: Customer.find(params[:id])\n', + }, + function: 'show', + context: { + pre: ['\n', ' def show\n'], + post: [' end\n', ' end\n'], + }, + }, + 'ruby' + ) + ).toEqual("api/customers_controller.rb:15 in `show'"); + }); + }); + + describe('with a RUM stackframe', () => { + it('renders', () => { + expect( + getRenderedStackframeText( + { + library_frame: false, + exclude_from_grouping: false, + filename: 'static/js/main.616809fb.js', + abs_path: 'http://opbeans-frontend:3000/static/js/main.616809fb.js', + sourcemap: { + error: + 'No Sourcemap available for ServiceName opbeans-rum, ServiceVersion 2020-08-25 02:09:37, Path http://opbeans-frontend:3000/static/js/main.616809fb.js.', + updated: false, + }, + line: { number: 319, column: 3842 }, + function: 'unstable_runWithPriority', + }, + 'javascript' + ) + ).toEqual( + 'at unstable_runWithPriority (static/js/main.616809fb.js:319:3842)' + ); + }); + }); +}); diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx index 5891895629318..dfeb537b04865 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx @@ -4,14 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment } from 'react'; +import React, { ComponentType } from 'react'; import styled from 'styled-components'; -import { IStackframe } from '../../../../typings/es_schemas/raw/fields/stackframe'; +import { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe'; import { fontFamilyCode, fontSize, px, units } from '../../../style/variables'; +import { + CSharpFrameHeadingRenderer, + DefaultFrameHeadingRenderer, + FrameHeadingRendererProps, + JavaFrameHeadingRenderer, + JavaScriptFrameHeadingRenderer, + RubyFrameHeadingRenderer, +} from './frame_heading_renderers'; const FileDetails = styled.div` color: ${({ theme }) => theme.eui.euiColorDarkShade}; - padding: ${px(units.half)} 0; + line-height: 1.5; /* matches the line-hight of the accordion container button */ + padding: ${px(units.eighth)} 0; font-family: ${fontFamilyCode}; font-size: ${fontSize}; `; @@ -25,29 +34,37 @@ const AppFrameFileDetail = styled.span` `; interface Props { - stackframe: IStackframe; + codeLanguage?: string; + stackframe: Stackframe; isLibraryFrame: boolean; } -function FrameHeading({ stackframe, isLibraryFrame }: Props) { - const FileDetail = isLibraryFrame +function FrameHeading({ codeLanguage, stackframe, isLibraryFrame }: Props) { + const FileDetail: ComponentType = isLibraryFrame ? LibraryFrameFileDetail : AppFrameFileDetail; - const lineNumber = stackframe.line?.number ?? 0; - - const name = - 'filename' in stackframe ? stackframe.filename : stackframe.classname; + let Renderer: ComponentType; + switch (codeLanguage?.toString().toLowerCase()) { + case 'c#': + Renderer = CSharpFrameHeadingRenderer; + break; + case 'java': + Renderer = JavaFrameHeadingRenderer; + break; + case 'javascript': + Renderer = JavaScriptFrameHeadingRenderer; + break; + case 'ruby': + Renderer = RubyFrameHeadingRenderer; + break; + default: + Renderer = DefaultFrameHeadingRenderer; + break; + } return ( - - {name} in{' '} - {stackframe.function} - {lineNumber > 0 && ( - - {' at '} - line {lineNumber} - - )} + + ); } diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.test.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.test.tsx index a83fb44e6aa37..7d22b56714f7e 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.test.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { shallow } from 'enzyme'; +import { renderWithTheme } from '../../../utils/testHelpers'; import { LibraryStacktrace } from './LibraryStacktrace'; describe('LibraryStacktrace', () => { @@ -13,8 +13,13 @@ describe('LibraryStacktrace', () => { describe('with no stack frames', () => { it('renders null', () => { const props = { id: 'testId', stackframes: [] }; + const { queryByTestId } = renderWithTheme( + + ); - expect(shallow().html()).toBeNull(); + expect( + queryByTestId('LibraryStacktraceAccordion') + ).not.toBeInTheDocument(); }); }); @@ -24,10 +29,11 @@ describe('LibraryStacktrace', () => { id: 'testId', stackframes: [{ filename: 'testFilename', line: { number: 1 } }], }; + const { queryByTestId } = renderWithTheme( + + ); - expect( - shallow().find('EuiAccordion') - ).toHaveLength(1); + expect(queryByTestId('LibraryStacktraceAccordion')).toBeInTheDocument(); }); }); }); diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.tsx index 703ade3b049ac..32180cd90f783 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.tsx @@ -8,17 +8,17 @@ import { EuiAccordion } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import styled from 'styled-components'; -import { IStackframe } from '../../../../typings/es_schemas/raw/fields/stackframe'; -import { Stackframe } from './Stackframe'; -import { px, unit } from '../../../style/variables'; +import { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe'; +import { px, units } from '../../../style/variables'; +import { Stackframe as StackframeComponent } from './Stackframe'; -const FramesContainer = styled('div')` - padding-left: ${px(unit)}; +const LibraryStacktraceAccordion = styled(EuiAccordion)` + margin: ${px(units.quarter)} 0; `; interface Props { codeLanguage?: string; - stackframes: IStackframe[]; + stackframes: Stackframe[]; id: string; } @@ -28,7 +28,7 @@ export function LibraryStacktrace({ codeLanguage, id, stackframes }: Props) { } return ( - - - {stackframes.map((stackframe, i) => ( - - ))} - - + {stackframes.map((stackframe, i) => ( + + ))} + ); } diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/Stackframe.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/Stackframe.tsx index 0c60a9aaace07..a552491297d55 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/Stackframe.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/Stackframe.tsx @@ -4,21 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiAccordion } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; -import { EuiAccordion } from '@elastic/eui'; import { - IStackframe, - IStackframeWithLineContext, + Stackframe as StackframeType, + StackframeWithLineContext, } from '../../../../typings/es_schemas/raw/fields/stackframe'; import { borderRadius, fontFamilyCode, fontSize, } from '../../../style/variables'; -import { FrameHeading } from './FrameHeading'; import { Context } from './Context'; +import { FrameHeading } from './FrameHeading'; import { Variables } from './Variables'; +import { px, units } from '../../../style/variables'; const ContextContainer = styled.div<{ isLibraryFrame: boolean }>` position: relative; @@ -32,8 +33,13 @@ const ContextContainer = styled.div<{ isLibraryFrame: boolean }>` : theme.eui.euiColorLightestShade}; `; +// Indent the non-context frames the same amount as the accordion control +const NoContextFrameHeadingWrapper = styled.div` + margin-left: ${px(units.unit + units.half + units.eighth)}; +`; + interface Props { - stackframe: IStackframe; + stackframe: StackframeType; codeLanguage?: string; id: string; initialIsOpen?: boolean; @@ -49,14 +55,24 @@ export function Stackframe({ }: Props) { if (!hasLineContext(stackframe)) { return ( - + + + ); } return ( + } id={id} initialIsOpen={initialIsOpen} @@ -74,7 +90,7 @@ export function Stackframe({ } function hasLineContext( - stackframe: IStackframe -): stackframe is IStackframeWithLineContext { + stackframe: StackframeType +): stackframe is StackframeWithLineContext { return stackframe.line?.hasOwnProperty('context') || false; } diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/Variables.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/Variables.tsx index 07b5ed6868df5..04c49a60a0436 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/Variables.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/Variables.tsx @@ -9,7 +9,7 @@ import { EuiAccordion } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { borderRadius, px, unit, units } from '../../../style/variables'; -import { IStackframe } from '../../../../typings/es_schemas/raw/fields/stackframe'; +import { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe'; import { KeyValueTable } from '../KeyValueTable'; import { flattenObject } from '../../../utils/flattenObject'; @@ -20,7 +20,7 @@ const VariablesContainer = styled.div` `; interface Props { - vars: IStackframe['vars']; + vars: Stackframe['vars']; } export function Variables({ vars }: Props) { diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/Stackframe.test.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/Stackframe.test.tsx index 1c5db4e82f069..c502237235578 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/Stackframe.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/Stackframe.test.tsx @@ -6,9 +6,9 @@ import React from 'react'; import { ReactWrapper, shallow } from 'enzyme'; -import { IStackframe } from '../../../../../typings/es_schemas/raw/fields/stackframe'; +import { Stackframe } from '../../../../../typings/es_schemas/raw/fields/stackframe'; import { mountWithTheme } from '../../../../utils/testHelpers'; -import { Stackframe } from '../Stackframe'; +import { Stackframe as StackframeComponent } from '../Stackframe'; import stacktracesMock from './stacktraces.json'; describe('Stackframe', () => { @@ -17,7 +17,7 @@ describe('Stackframe', () => { beforeEach(() => { const stackframe = stacktracesMock[0]; wrapper = mountWithTheme( - + ); }); @@ -39,9 +39,9 @@ describe('Stackframe', () => { describe('when stackframe does not have source lines', () => { let wrapper: ReactWrapper; beforeEach(() => { - const stackframe = { line: {} } as IStackframe; + const stackframe = { line: {} } as Stackframe; wrapper = mountWithTheme( - + ); }); @@ -57,9 +57,9 @@ describe('Stackframe', () => { }); it('should respect isLibraryFrame', () => { - const stackframe = { line: {} } as IStackframe; + const stackframe = { line: {} } as Stackframe; const wrapper = shallow( - + ); expect(wrapper.find('FrameHeading').prop('isLibraryFrame')).toBe(true); }); diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap index 86e6b188ef2a9..1bfeb020a2ab5 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap @@ -1,17 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Stackframe when stackframe has source lines should render correctly 1`] = ` -.c0 { - color: #69707d; - padding: 8px 0; - font-family: "Roboto Mono",Consolas,Menlo,Courier,monospace; - font-size: 14px; -} - -.c1 { - color: #000000; -} - .c3 { position: relative; border-radius: 4px; @@ -95,6 +84,18 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] z-index: 2; } +.c0 { + color: #69707d; + line-height: 1.5; + padding: 2px 0; + font-family: "Roboto Mono",Consolas,Menlo,Courier,monospace; + font-size: 14px; +} + +.c1 { + color: #000000; +} + .c2 { position: relative; font-family: "Roboto Mono",Consolas,Menlo,Courier,monospace; @@ -221,35 +222,92 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] } } > - +
- - - server/routes.js - - - in - - - - <anonymous> - - - at - - - line - 307 - - + ", + "library_frame": false, + "line": Object { + "context": " client.query('SELECT id FROM customers WHERE id=$1', [req.body.customer_id], next())", + "number": 307, + }, + } + } + > + + + server/routes.js + + + in + + + + <anonymous> + + + at + + + line + 307 + + +
@@ -383,8 +441,8 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] "$$typeof": Symbol(react.forward_ref), "attrs": Array [], "componentStyle": ComponentStyle { - "baseHash": -2021127760, - "componentId": "sc-fzoLsD", + "baseHash": -1474970742, + "componentId": "sc-Axmtr", "isStatic": false, "rules": Array [ " @@ -401,7 +459,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] "foldedComponentIds": Array [], "render": [Function], "shouldForwardProp": undefined, - "styledComponentId": "sc-fzoLsD", + "styledComponentId": "sc-Axmtr", "target": "code", "toString": [Function], "warnTooManyClasses": [Function], @@ -413,8 +471,8 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] "$$typeof": Symbol(react.forward_ref), "attrs": Array [], "componentStyle": ComponentStyle { - "baseHash": 1280172402, - "componentId": "sc-fzozJi", + "baseHash": 1882630949, + "componentId": "sc-AxheI", "isStatic": false, "rules": Array [ " @@ -439,7 +497,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] "foldedComponentIds": Array [], "render": [Function], "shouldForwardProp": undefined, - "styledComponentId": "sc-fzozJi", + "styledComponentId": "sc-AxheI", "target": "pre", "toString": [Function], "warnTooManyClasses": [Function], @@ -608,8 +666,8 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] "$$typeof": Symbol(react.forward_ref), "attrs": Array [], "componentStyle": ComponentStyle { - "baseHash": -2021127760, - "componentId": "sc-fzoLsD", + "baseHash": -1474970742, + "componentId": "sc-Axmtr", "isStatic": false, "rules": Array [ " @@ -626,7 +684,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] "foldedComponentIds": Array [], "render": [Function], "shouldForwardProp": undefined, - "styledComponentId": "sc-fzoLsD", + "styledComponentId": "sc-Axmtr", "target": "code", "toString": [Function], "warnTooManyClasses": [Function], @@ -638,8 +696,8 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] "$$typeof": Symbol(react.forward_ref), "attrs": Array [], "componentStyle": ComponentStyle { - "baseHash": 1280172402, - "componentId": "sc-fzozJi", + "baseHash": 1882630949, + "componentId": "sc-AxheI", "isStatic": false, "rules": Array [ " @@ -664,7 +722,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] "foldedComponentIds": Array [], "render": [Function], "shouldForwardProp": undefined, - "styledComponentId": "sc-fzozJi", + "styledComponentId": "sc-AxheI", "target": "pre", "toString": [Function], "warnTooManyClasses": [Function], @@ -834,8 +892,8 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] "$$typeof": Symbol(react.forward_ref), "attrs": Array [], "componentStyle": ComponentStyle { - "baseHash": -2021127760, - "componentId": "sc-fzoLsD", + "baseHash": -1474970742, + "componentId": "sc-Axmtr", "isStatic": false, "rules": Array [ " @@ -852,7 +910,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] "foldedComponentIds": Array [], "render": [Function], "shouldForwardProp": undefined, - "styledComponentId": "sc-fzoLsD", + "styledComponentId": "sc-Axmtr", "target": "code", "toString": [Function], "warnTooManyClasses": [Function], @@ -864,8 +922,8 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] "$$typeof": Symbol(react.forward_ref), "attrs": Array [], "componentStyle": ComponentStyle { - "baseHash": 1280172402, - "componentId": "sc-fzozJi", + "baseHash": 1882630949, + "componentId": "sc-AxheI", "isStatic": false, "rules": Array [ " @@ -890,7 +948,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] "foldedComponentIds": Array [], "render": [Function], "shouldForwardProp": undefined, - "styledComponentId": "sc-fzozJi", + "styledComponentId": "sc-AxheI", "target": "pre", "toString": [Function], "warnTooManyClasses": [Function], @@ -1070,8 +1128,8 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] "$$typeof": Symbol(react.forward_ref), "attrs": Array [], "componentStyle": ComponentStyle { - "baseHash": -2021127760, - "componentId": "sc-fzoLsD", + "baseHash": -1474970742, + "componentId": "sc-Axmtr", "isStatic": false, "rules": Array [ " @@ -1088,7 +1146,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] "foldedComponentIds": Array [], "render": [Function], "shouldForwardProp": undefined, - "styledComponentId": "sc-fzoLsD", + "styledComponentId": "sc-Axmtr", "target": "code", "toString": [Function], "warnTooManyClasses": [Function], @@ -1100,8 +1158,8 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] "$$typeof": Symbol(react.forward_ref), "attrs": Array [], "componentStyle": ComponentStyle { - "baseHash": 1280172402, - "componentId": "sc-fzozJi", + "baseHash": 1882630949, + "componentId": "sc-AxheI", "isStatic": false, "rules": Array [ " @@ -1126,7 +1184,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] "foldedComponentIds": Array [], "render": [Function], "shouldForwardProp": undefined, - "styledComponentId": "sc-fzozJi", + "styledComponentId": "sc-AxheI", "target": "pre", "toString": [Function], "warnTooManyClasses": [Function], @@ -1323,8 +1381,8 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] "$$typeof": Symbol(react.forward_ref), "attrs": Array [], "componentStyle": ComponentStyle { - "baseHash": -2021127760, - "componentId": "sc-fzoLsD", + "baseHash": -1474970742, + "componentId": "sc-Axmtr", "isStatic": false, "rules": Array [ " @@ -1341,7 +1399,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] "foldedComponentIds": Array [], "render": [Function], "shouldForwardProp": undefined, - "styledComponentId": "sc-fzoLsD", + "styledComponentId": "sc-Axmtr", "target": "code", "toString": [Function], "warnTooManyClasses": [Function], @@ -1353,8 +1411,8 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] "$$typeof": Symbol(react.forward_ref), "attrs": Array [], "componentStyle": ComponentStyle { - "baseHash": 1280172402, - "componentId": "sc-fzozJi", + "baseHash": 1882630949, + "componentId": "sc-AxheI", "isStatic": false, "rules": Array [ " @@ -1379,7 +1437,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] "foldedComponentIds": Array [], "render": [Function], "shouldForwardProp": undefined, - "styledComponentId": "sc-fzozJi", + "styledComponentId": "sc-AxheI", "target": "pre", "toString": [Function], "warnTooManyClasses": [Function], diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/index.test.ts b/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/index.test.ts index d268e0e6cee9e..cd8bf90301836 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/index.test.ts +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/index.test.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IStackframe } from '../../../../../typings/es_schemas/raw/fields/stackframe'; +import { Stackframe } from '../../../../../typings/es_schemas/raw/fields/stackframe'; import { getGroupedStackframes } from '../index'; import stacktracesMock from './stacktraces.json'; describe('Stacktrace/index', () => { describe('getGroupedStackframes', () => { it('should collapse the library frames into a set of grouped stackframes', () => { - const result = getGroupedStackframes(stacktracesMock as IStackframe[]); + const result = getGroupedStackframes(stacktracesMock as Stackframe[]); expect(result).toMatchSnapshot(); }); @@ -37,7 +37,7 @@ describe('Stacktrace/index', () => { exclude_from_grouping: false, filename: 'file-d.txt', }, - ] as IStackframe[]; + ] as Stackframe[]; const result = getGroupedStackframes(stackframes); @@ -89,7 +89,7 @@ describe('Stacktrace/index', () => { exclude_from_grouping: false, filename: 'file-b.txt', }, - ] as IStackframe[]; + ] as Stackframe[]; const result = getGroupedStackframes(stackframes); expect(result).toEqual([ { @@ -129,7 +129,7 @@ describe('Stacktrace/index', () => { exclude_from_grouping: true, filename: 'file-b.txt', }, - ] as IStackframe[]; + ] as Stackframe[]; const result = getGroupedStackframes(stackframes); expect(result).toEqual([ { @@ -158,14 +158,14 @@ describe('Stacktrace/index', () => { }); it('should handle empty stackframes', () => { - const result = getGroupedStackframes([] as IStackframe[]); + const result = getGroupedStackframes([] as Stackframe[]); expect(result).toHaveLength(0); }); it('should handle one stackframe', () => { const result = getGroupedStackframes([ stacktracesMock[0], - ] as IStackframe[]); + ] as Stackframe[]); expect(result).toHaveLength(1); expect(result[0].stackframes).toHaveLength(1); }); diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading_renderers/c_sharp_frame_heading_renderer.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading_renderers/c_sharp_frame_heading_renderer.tsx new file mode 100644 index 0000000000000..ed0dd6b42afe1 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading_renderers/c_sharp_frame_heading_renderer.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { FrameHeadingRendererProps } from './'; +import { DefaultFrameHeadingRenderer } from './default_frame_heading_renderer'; + +export function CSharpFrameHeadingRenderer({ + stackframe, + fileDetailComponent: FileDetail, +}: FrameHeadingRendererProps) { + const { classname, filename, function: fn } = stackframe; + const lineNumber = stackframe.line?.number ?? 0; + + if (classname) { + return ( + <> + {classname} + {' in '} + {fn} + {' in '} + {filename} + {lineNumber > 0 && ( + <> + {' at '} + line {lineNumber} + + )} + + ); + } else { + return ( + + ); + } +} diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading_renderers/default_frame_heading_renderer.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading_renderers/default_frame_heading_renderer.tsx new file mode 100644 index 0000000000000..7e70a5e849525 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading_renderers/default_frame_heading_renderer.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { FrameHeadingRendererProps } from './'; + +export function DefaultFrameHeadingRenderer({ + stackframe, + fileDetailComponent: FileDetail, +}: FrameHeadingRendererProps) { + const lineNumber = stackframe.line?.number ?? 0; + + const name = + 'filename' in stackframe ? stackframe.filename : stackframe.classname; + + return ( + <> + {name} in{' '} + {stackframe.function} + {lineNumber > 0 && ( + <> + {' at '} + line {lineNumber} + + )} + + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading_renderers/index.ts b/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading_renderers/index.ts new file mode 100644 index 0000000000000..d81d178abfcb9 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading_renderers/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ComponentType } from 'react'; +import { Stackframe } from '../../../../../typings/es_schemas/raw/fields/stackframe'; + +export interface FrameHeadingRendererProps { + fileDetailComponent: ComponentType; + stackframe: Stackframe; +} + +export { CSharpFrameHeadingRenderer } from './c_sharp_frame_heading_renderer'; +export { DefaultFrameHeadingRenderer } from './default_frame_heading_renderer'; +export { JavaFrameHeadingRenderer } from './java_frame_heading_renderer'; +export { JavaScriptFrameHeadingRenderer } from './java_script_frame_heading_renderer'; +export { RubyFrameHeadingRenderer } from './ruby_frame_heading_renderer'; diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading_renderers/java_frame_heading_renderer.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading_renderers/java_frame_heading_renderer.tsx new file mode 100644 index 0000000000000..7ded2e5263fda --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading_renderers/java_frame_heading_renderer.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { FrameHeadingRendererProps } from './'; + +export function JavaFrameHeadingRenderer({ + stackframe, + fileDetailComponent: FileDetail, +}: FrameHeadingRendererProps) { + const { classname, filename, function: fn } = stackframe; + const lineNumber = stackframe.line?.number ?? 0; + + return ( + <> + at {[classname, fn].join('.')}( + + {filename} + {lineNumber > 0 && `:${lineNumber}`} + + ) + + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading_renderers/java_script_frame_heading_renderer.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading_renderers/java_script_frame_heading_renderer.tsx new file mode 100644 index 0000000000000..b0ee158be75a2 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading_renderers/java_script_frame_heading_renderer.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { FrameHeadingRendererProps } from './'; + +export function JavaScriptFrameHeadingRenderer({ + stackframe, + fileDetailComponent: FileDetail, +}: FrameHeadingRendererProps) { + const { classname, filename, function: fn } = stackframe; + const lineNumber = stackframe.line?.number ?? 0; + const columnNumber = stackframe.line?.column ?? 0; + + return ( + <> + at{' '} + + {classname && `${classname}.`} + {fn} + {' '} + ( + + {filename} + {lineNumber > 0 && `:${lineNumber}`} + {lineNumber > 0 && columnNumber > 0 && `:${columnNumber}`} + + ) + + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading_renderers/ruby_frame_heading_renderer.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading_renderers/ruby_frame_heading_renderer.tsx new file mode 100644 index 0000000000000..2220a91f25c04 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading_renderers/ruby_frame_heading_renderer.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { FrameHeadingRendererProps } from './'; + +export function RubyFrameHeadingRenderer({ + stackframe, + fileDetailComponent: FileDetail, +}: FrameHeadingRendererProps) { + const { filename, function: fn } = stackframe; + const lineNumber = stackframe.line?.number ?? 0; + + return ( + <> + + {filename} + {lineNumber > 0 && `:${lineNumber}`} + + {' in '} + `{fn}' + + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/index.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/index.tsx index b37146f3b3be5..b9c4986220fad 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/index.tsx @@ -4,17 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isEmpty, last } from 'lodash'; import React, { Fragment } from 'react'; -import { IStackframe } from '../../../../typings/es_schemas/raw/fields/stackframe'; +import { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe'; import { EmptyMessage } from '../../shared/EmptyMessage'; import { LibraryStacktrace } from './LibraryStacktrace'; -import { Stackframe } from './Stackframe'; +import { Stackframe as StackframeComponent } from './Stackframe'; interface Props { - stackframes?: IStackframe[]; + stackframes?: Stackframe[]; codeLanguage?: string; } @@ -42,13 +41,11 @@ export function Stacktrace({ stackframes = [], codeLanguage }: Props) { if (group.isLibraryFrame && groups.length > 1) { return ( - - ); } @@ -56,8 +53,7 @@ export function Stacktrace({ stackframes = [], codeLanguage }: Props) { // non-library frame return group.stackframes.map((stackframe, idx) => ( - {idx > 0 && } - 1} @@ -66,7 +62,6 @@ export function Stacktrace({ stackframes = [], codeLanguage }: Props) { )); })} - ); } @@ -74,10 +69,10 @@ export function Stacktrace({ stackframes = [], codeLanguage }: Props) { interface StackframesGroup { isLibraryFrame: boolean; excludeFromGrouping: boolean; - stackframes: IStackframe[]; + stackframes: Stackframe[]; } -export function getGroupedStackframes(stackframes: IStackframe[]) { +export function getGroupedStackframes(stackframes: Stackframe[]) { return stackframes.reduce((acc, stackframe) => { const prevGroup = last(acc); const shouldAppend = diff --git a/x-pack/plugins/apm/typings/es_schemas/raw/error_raw.ts b/x-pack/plugins/apm/typings/es_schemas/raw/error_raw.ts index 8e49d02beb908..b8eb79aabdcc5 100644 --- a/x-pack/plugins/apm/typings/es_schemas/raw/error_raw.ts +++ b/x-pack/plugins/apm/typings/es_schemas/raw/error_raw.ts @@ -12,7 +12,7 @@ import { Kubernetes } from './fields/kubernetes'; import { Page } from './fields/page'; import { Process } from './fields/process'; import { Service } from './fields/service'; -import { IStackframe } from './fields/stackframe'; +import { Stackframe } from './fields/stackframe'; import { Url } from './fields/url'; import { User } from './fields/user'; import { Observer } from './fields/observer'; @@ -23,16 +23,20 @@ interface Processor { } export interface Exception { + attributes?: { + response?: string; + }; + code?: string; message?: string; // either message or type are given type?: string; module?: string; handled?: boolean; - stacktrace?: IStackframe[]; + stacktrace?: Stackframe[]; } interface Log { message: string; - stacktrace?: IStackframe[]; + stacktrace?: Stackframe[]; } export interface ErrorRaw extends APMBaseDoc { diff --git a/x-pack/plugins/apm/typings/es_schemas/raw/fields/stackframe.ts b/x-pack/plugins/apm/typings/es_schemas/raw/fields/stackframe.ts index 05b0eb88da40b..901b02814371a 100644 --- a/x-pack/plugins/apm/typings/es_schemas/raw/fields/stackframe.ts +++ b/x-pack/plugins/apm/typings/es_schemas/raw/fields/stackframe.ts @@ -4,27 +4,39 @@ * you may not use this file except in compliance with the Elastic License. */ -type IStackframeBase = { - function?: string; - library_frame?: boolean; - exclude_from_grouping?: boolean; +interface Line { + column?: number; + number: number; +} + +interface Sourcemap { + error?: string; + updated?: boolean; +} + +interface StackframeBase { + abs_path?: string; + classname?: string; context?: { post?: string[]; pre?: string[]; }; + exclude_from_grouping?: boolean; + filename?: string; + function?: string; + module?: string; + library_frame?: boolean; + line?: Line; + sourcemap?: Sourcemap; vars?: { [key: string]: unknown; }; - line?: { - number: number; - }; -} & ({ classname: string } | { filename: string }); +} -export type IStackframeWithLineContext = IStackframeBase & { - line: { - number: number; +export type StackframeWithLineContext = StackframeBase & { + line: Line & { context: string; }; }; -export type IStackframe = IStackframeBase | IStackframeWithLineContext; +export type Stackframe = StackframeBase | StackframeWithLineContext; diff --git a/x-pack/plugins/apm/typings/es_schemas/raw/span_raw.ts b/x-pack/plugins/apm/typings/es_schemas/raw/span_raw.ts index f6c4fce76f134..5c2e391059783 100644 --- a/x-pack/plugins/apm/typings/es_schemas/raw/span_raw.ts +++ b/x-pack/plugins/apm/typings/es_schemas/raw/span_raw.ts @@ -5,7 +5,7 @@ */ import { APMBaseDoc } from './apm_base_doc'; -import { IStackframe } from './fields/stackframe'; +import { Stackframe } from './fields/stackframe'; import { Observer } from './fields/observer'; interface Processor { @@ -24,7 +24,7 @@ export interface SpanRaw extends APMBaseDoc { duration: { us: number }; id: string; name: string; - stacktrace?: IStackframe[]; + stacktrace?: Stackframe[]; subtype?: string; sync?: boolean; type: string;