Skip to content

Commit 3406e5b

Browse files
committed
Add copyable bug report generation on errors
Generate a structured, anonymous bug report on every error: build info (volview version, git SHA, vtk.js/itk-wasm versions), browser UA, error stack trace, dataset metadata (dimensions, scalar type, source format, segment group save format). Users copy it from a button in the notification center title bar. Uses dependency injection (setBugReportGenerator) to avoid circular deps between the message store and dataset stores.
1 parent 5ca902e commit 3406e5b

File tree

3 files changed

+19
-69
lines changed

3 files changed

+19
-69
lines changed

src/store/messages.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,17 @@ export const useMessageStore = defineStore('message', {
5656
addError(title: string, opts?: ErrorOptions) {
5757
console.error(title, opts?.error ?? opts?.details);
5858

59-
const details = opts?.details ?? opts?.error?.stack;
60-
61-
const id = this._addMessage(
62-
{ type: MessageType.Error, title },
63-
{ details, persist: opts?.persist ?? false }
59+
return this._addMessage(
60+
{
61+
type: MessageType.Error,
62+
title,
63+
bugReport: generateBugReport(opts?.error),
64+
},
65+
{
66+
details: opts?.details ?? opts?.error?.stack,
67+
persist: opts?.persist ?? false,
68+
}
6469
);
65-
66-
this.byID[id].bugReport = generateBugReport(opts?.error);
67-
68-
return id;
6970
},
7071
/**
7172
* Adds a warning message.

src/utils/bugReport.ts

Lines changed: 8 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,40 +9,8 @@ const MAX_ERROR_LENGTH = 4000;
99

1010
const COMPOUND_EXTENSIONS = ['nii.gz', 'iwi.cbor', 'seg.nrrd'];
1111

12-
const parseBrowserInfo = (): string => {
13-
if (typeof navigator === 'undefined') return 'unknown';
14-
15-
const { userAgent: ua } = navigator;
16-
17-
const patterns: [string, RegExp][] = [
18-
['Firefox', /Firefox\/([\d.]+)/],
19-
['Edge', /Edg\/([\d.]+)/],
20-
['Chrome', /Chrome\/([\d.]+)/],
21-
['Safari', /Version\/([\d.]+).*Safari/],
22-
];
23-
24-
const match = patterns
25-
.map(([name, re]) => {
26-
const m = ua.match(re);
27-
return m ? `${name} ${m[1]}` : null;
28-
})
29-
.find(Boolean);
30-
31-
const browser = match ?? 'Unknown';
32-
33-
const os = ['Windows', 'Mac', 'Linux', 'Android', 'iPhone', 'iPad'].find(
34-
(name) => ua.includes(name)
35-
);
36-
37-
const osLabel =
38-
os === 'Mac'
39-
? 'macos'
40-
: os === 'iPhone' || os === 'iPad'
41-
? 'ios'
42-
: (os?.toLowerCase() ?? 'unknown');
43-
44-
return `${browser} (${osLabel})`;
45-
};
12+
const getBrowserInfo = (): string =>
13+
typeof navigator !== 'undefined' ? navigator.userAgent : 'unknown';
4614

4715
const getSourceFormat = (name: string, isDicom: boolean): string => {
4816
if (isDicom) return 'DICOM';
@@ -107,22 +75,18 @@ export const generateBugReport = (error?: Error): string => {
10775
const lines = [
10876
'--- VolView Bug Report ---',
10977
`Build: volview ${versions.volview} (${sha}) | vtk.js: ${versions['vtk.js']}, itk-wasm: ${versions['itk-wasm']}`,
110-
`Browser: ${parseBrowserInfo()}`,
78+
`Browser: ${getBrowserInfo()}`,
11179
'',
11280
'Error:',
11381
formatError(error),
11482
];
11583

116-
try {
117-
const datasets = collectDatasetInfo();
118-
const segmentGroupStore = useSegmentGroupStore();
84+
const datasets = collectDatasetInfo();
85+
const segmentGroupStore = useSegmentGroupStore();
11986

120-
lines.push('', `Datasets: ${datasets.length}`);
121-
lines.push(...datasets);
122-
lines.push(`Save format: ${segmentGroupStore.saveFormat}`);
123-
} catch {
124-
lines.push('', 'Datasets: unavailable');
125-
}
87+
lines.push('', `Datasets: ${datasets.length}`);
88+
lines.push(...datasets);
89+
lines.push(`Save format: ${segmentGroupStore.saveFormat}`);
12690

12791
lines.push('--- End Report ---');
12892

tests/specs/bug-report.e2e.ts

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { volViewPage } from '../pageobjects/volview.page';
22
import { writeManifestToFile } from './utils';
33

44
describe('Bug report generation', () => {
5-
it('should attach bug report to error messages', async () => {
5+
it('should show Copy Bug Report button on error', async () => {
66
// Trigger an error by loading a malformed URL
77
const manifest = { resources: [{ url: 'bad-url-to-trigger-error' }] };
88
await writeManifestToFile(manifest, 'bugReportTest.json');
@@ -22,20 +22,5 @@ describe('Bug report generation', () => {
2222
},
2323
{ timeout: 5000, timeoutMsg: 'Expected Copy Bug Report button' }
2424
);
25-
26-
// Verify bug report content via the store
27-
const report = await browser.execute(() => {
28-
// Access the Pinia store from the app instance
29-
const app = document.querySelector('#app') as any;
30-
const pinia = app?.__vue_app__?.config?.globalProperties?.$pinia;
31-
if (!pinia) return '';
32-
const store = pinia.state.value.message;
33-
if (!store) return '';
34-
const firstId = store.msgList[0];
35-
return store.byID[firstId]?.bugReport ?? '';
36-
});
37-
38-
expect(report).toContain('--- VolView Bug Report ---');
39-
expect(report).toContain('Browser:');
4025
});
4126
});

0 commit comments

Comments
 (0)