Commit 26303a9f authored by Olena Horal-Koretska's avatar Olena Horal-Koretska Committed by Kushal Pandya

Handle empty stacktrace and entries with no code

Add logic for entries with no code & empty stacktrace
parent 27646fff
...@@ -27,6 +27,8 @@ export default { ...@@ -27,6 +27,8 @@ export default {
:lines="entry.context" :lines="entry.context"
:file-path="entry.filename" :file-path="entry.filename"
:error-line="entry.lineNo" :error-line="entry.lineNo"
:error-fn="entry.function"
:error-column="entry.colNo"
:expanded="isFirstEntry(index)" :expanded="isFirstEntry(index)"
/> />
</div> </div>
......
<script> <script>
import { __, sprintf } from '~/locale';
import { GlTooltip } from '@gitlab/ui'; import { GlTooltip } from '@gitlab/ui';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue'; import FileIcon from '~/vue_shared/components/file_icon.vue';
...@@ -22,9 +23,20 @@ export default { ...@@ -22,9 +23,20 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
errorFn: {
type: String,
required: false,
default: '',
},
errorLine: { errorLine: {
type: Number, type: Number,
required: true, required: false,
default: 0,
},
errorColumn: {
type: Number,
required: false,
default: 0,
}, },
expanded: { expanded: {
type: Boolean, type: Boolean,
...@@ -38,12 +50,23 @@ export default { ...@@ -38,12 +50,23 @@ export default {
}; };
}, },
computed: { computed: {
linesLength() { hasCode() {
return this.lines.length; return Boolean(this.lines.length);
}, },
collapseIcon() { collapseIcon() {
return this.isExpanded ? 'chevron-down' : 'chevron-right'; return this.isExpanded ? 'chevron-down' : 'chevron-right';
}, },
noCodeFn() {
return this.errorFn ? sprintf(__('in %{errorFn} '), { errorFn: this.errorFn }) : '';
},
noCodeLine() {
return this.errorLine
? sprintf(__('at line %{errorLine}%{errorColumn}'), {
errorLine: this.errorLine,
errorColumn: this.errorColumn ? `:${this.errorColumn}` : '',
})
: '';
},
}, },
methods: { methods: {
isHighlighted(lineNum) { isHighlighted(lineNum) {
...@@ -66,27 +89,31 @@ export default { ...@@ -66,27 +89,31 @@ export default {
<template> <template>
<div class="file-holder"> <div class="file-holder">
<div ref="header" class="file-title file-title-flex-parent"> <div ref="header" class="file-title file-title-flex-parent">
<div class="file-header-content "> <div class="file-header-content d-flex align-content-center">
<div class="d-inline-block cursor-pointer" @click="toggle()"> <div v-if="hasCode" class="d-inline-block cursor-pointer" @click="toggle()">
<icon :name="collapseIcon" :size="16" aria-hidden="true" class="append-right-5" /> <icon :name="collapseIcon" :size="16" aria-hidden="true" class="append-right-5" />
</div> </div>
<div class="d-inline-block append-right-4"> <file-icon
<file-icon :file-name="filePath"
:file-name="filePath" :size="18"
:size="18" aria-hidden="true"
aria-hidden="true" css-classes="append-right-5"
css-classes="append-right-5" />
/> <strong
<strong v-gl-tooltip :title="filePath" class="file-title-name" data-container="body"> v-gl-tooltip
{{ filePath }} :title="filePath"
</strong> class="file-title-name d-inline-block overflow-hidden text-truncate"
</div> :class="{ 'limited-width': !hasCode }"
data-container="body"
>
{{ filePath }}
</strong>
<clipboard-button <clipboard-button
:title="__('Copy file path')" :title="__('Copy file path')"
:text="filePath" :text="filePath"
css-class="btn-default btn-transparent btn-clipboard" css-class="btn-default btn-transparent btn-clipboard position-static"
/> />
<span v-if="!hasCode" class="text-tertiary">{{ noCodeFn }}{{ noCodeLine }}</span>
</div> </div>
</div> </div>
......
export const stacktrace = state => state.stacktraceData.stack_trace_entries.reverse(); export const stacktrace = state =>
state.stacktraceData.stack_trace_entries
? state.stacktraceData.stack_trace_entries.reverse()
: [];
export default () => {}; export default () => {};
...@@ -12,6 +12,12 @@ ...@@ -12,6 +12,12 @@
} }
} }
.file-title-name {
&.limited-width {
max-width: 80%;
}
}
.line_content.old::before { .line_content.old::before {
content: none !important; content: none !important;
} }
......
---
title: Handle empty stacktrace and entries with no code
merge_request: 20458
author:
type: fixed
...@@ -20386,6 +20386,9 @@ msgstr "" ...@@ -20386,6 +20386,9 @@ msgstr ""
msgid "assign yourself" msgid "assign yourself"
msgstr "" msgstr ""
msgid "at line %{errorLine}%{errorColumn}"
msgstr ""
msgid "attach a new file" msgid "attach a new file"
msgstr "" msgstr ""
...@@ -20866,6 +20869,9 @@ msgstr "" ...@@ -20866,6 +20869,9 @@ msgstr ""
msgid "importing" msgid "importing"
msgstr "" msgstr ""
msgid "in %{errorFn} "
msgstr ""
msgid "in group %{link_to_group}" msgid "in group %{link_to_group}"
msgstr "" msgstr ""
......
...@@ -7,26 +7,23 @@ import Icon from '~/vue_shared/components/icon.vue'; ...@@ -7,26 +7,23 @@ import Icon from '~/vue_shared/components/icon.vue';
describe('Stacktrace Entry', () => { describe('Stacktrace Entry', () => {
let wrapper; let wrapper;
const lines = [
[22, ' def safe_thread(name, \u0026block)\n'],
[23, ' Thread.new do\n'],
[24, " Thread.current['sidekiq_label'] = name\n"],
[25, ' watchdog(name, \u0026block)\n'],
];
function mountComponent(props) { function mountComponent(props) {
wrapper = shallowMount(StackTraceEntry, { wrapper = shallowMount(StackTraceEntry, {
propsData: { propsData: {
filePath: 'sidekiq/util.rb', filePath: 'sidekiq/util.rb',
lines: [
[22, ' def safe_thread(name, \u0026block)\n'],
[23, ' Thread.new do\n'],
[24, " Thread.current['sidekiq_label'] = name\n"],
[25, ' watchdog(name, \u0026block)\n'],
],
errorLine: 24, errorLine: 24,
...props, ...props,
}, },
}); });
} }
beforeEach(() => {
mountComponent();
});
afterEach(() => { afterEach(() => {
if (wrapper) { if (wrapper) {
wrapper.destroy(); wrapper.destroy();
...@@ -34,16 +31,47 @@ describe('Stacktrace Entry', () => { ...@@ -34,16 +31,47 @@ describe('Stacktrace Entry', () => {
}); });
it('should render stacktrace entry collapsed', () => { it('should render stacktrace entry collapsed', () => {
mountComponent({ lines });
expect(wrapper.find(StackTraceEntry).exists()).toBe(true); expect(wrapper.find(StackTraceEntry).exists()).toBe(true);
expect(wrapper.find(ClipboardButton).exists()).toBe(true); expect(wrapper.find(ClipboardButton).exists()).toBe(true);
expect(wrapper.find(Icon).exists()).toBe(true); expect(wrapper.find(Icon).exists()).toBe(true);
expect(wrapper.find(FileIcon).exists()).toBe(true); expect(wrapper.find(FileIcon).exists()).toBe(true);
expect(wrapper.element.querySelectorAll('table').length).toBe(0); expect(wrapper.find('table').exists()).toBe(false);
}); });
it('should render stacktrace entry table expanded', () => { it('should render stacktrace entry table expanded', () => {
mountComponent({ expanded: true }); mountComponent({ expanded: true, lines });
expect(wrapper.element.querySelectorAll('tr.line_holder').length).toBe(4); expect(wrapper.find('table').exists()).toBe(true);
expect(wrapper.element.querySelectorAll('.line_content.old').length).toBe(1); expect(wrapper.findAll('tr.line_holder').length).toBe(4);
expect(wrapper.findAll('.line_content.old').length).toBe(1);
});
describe('no code block', () => {
const findFileHeaderContent = () => wrapper.find('.file-header-content').html();
it('should hide collapse icon and render error fn name and error line when there is no code block', () => {
const extraInfo = { errorLine: 34, errorFn: 'errorFn', errorColumn: 77 };
mountComponent({ expanded: false, lines: [], ...extraInfo });
expect(wrapper.find(Icon).exists()).toBe(false);
expect(findFileHeaderContent()).toContain(
`in ${extraInfo.errorFn} at line ${extraInfo.errorLine}:${extraInfo.errorColumn}`,
);
});
it('should render only lineNo:columnNO when there is no errorFn ', () => {
const extraInfo = { errorLine: 34, errorFn: null, errorColumn: 77 };
mountComponent({ expanded: false, lines: [], ...extraInfo });
expect(findFileHeaderContent()).not.toContain(`in ${extraInfo.errorFn}`);
expect(findFileHeaderContent()).toContain(`${extraInfo.errorLine}:${extraInfo.errorColumn}`);
});
it('should render only lineNo when there is no errorColumn ', () => {
const extraInfo = { errorLine: 34, errorFn: 'errorFn', errorColumn: null };
mountComponent({ expanded: false, lines: [], ...extraInfo });
expect(findFileHeaderContent()).toContain(
`in ${extraInfo.errorFn} at line ${extraInfo.errorLine}`,
);
expect(findFileHeaderContent()).not.toContain(`:${extraInfo.errorColumn}`);
});
}); });
}); });
import * as getters from '~/error_tracking/store/details/getters'; import * as getters from '~/error_tracking/store/details/getters';
describe('Sentry error details store getters', () => { describe('Sentry error details store getters', () => {
const state = {
stacktraceData: { stack_trace_entries: [1, 2] },
};
describe('stacktrace', () => { describe('stacktrace', () => {
it('should return empty stacktrace when there are no entries', () => {
const state = {
stacktraceData: { stack_trace_entries: null },
};
expect(getters.stacktrace(state)).toEqual([]);
});
it('should get stacktrace', () => { it('should get stacktrace', () => {
const state = {
stacktraceData: { stack_trace_entries: [1, 2] },
};
expect(getters.stacktrace(state)).toEqual([2, 1]); expect(getters.stacktrace(state)).toEqual([2, 1]);
}); });
}); });
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment