Clean up error reporting
Change-Id: I63bc4c0f6e2aa4c6ebae6be85860be3b422d6615
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
index 020762c..1a8f80f 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
@@ -1150,7 +1150,7 @@
revert dialog after revert button is pressed. */
this.restApiService.getChanges(0, query).then(changes => {
if (!changes) {
- console.error('changes is undefined');
+ this.reporting.error(new Error('changes is undefined'));
return;
}
this.$.confirmRevertDialog.populate(change, this.commitMessage, changes);
@@ -1164,7 +1164,7 @@
const query = `submissionid:${change.submission_id}`;
this.restApiService.getChanges(0, query).then(changes => {
if (!changes) {
- console.error('changes is undefined');
+ this.reporting.error(new Error('changes is undefined'));
return;
}
this.$.confirmRevertSubmissionDialog._populateRevertSubmissionMessage(
@@ -1442,7 +1442,7 @@
);
break;
default:
- console.error('invalid revert type');
+ this.reporting.error(new Error('invalid revert type'));
}
}
@@ -1794,7 +1794,7 @@
.getChanges(0, query, undefined, options)
.then(changes => {
if (!changes) {
- console.error('getChanges returns undefined');
+ this.reporting.error(new Error('getChanges returns undefined'));
return;
}
this.$.confirmCherrypick.updateChanges(changes);
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
index 6a4d5b1..51fc5b5 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
@@ -360,7 +360,7 @@
input: string
): Promise<AutocompleteSuggestion[]> {
if (!this.project) {
- console.error('no project specified');
+ this.reporting.error(new Error('no project specified'));
return Promise.resolve([]);
}
if (input.startsWith('refs/heads/')) {
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
index d0d0745..c3ee8e5 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
@@ -561,7 +561,7 @@
for (const splice of indexSplices) {
for (const account of splice.removed) {
if (!this._reviewersPendingRemove[type]) {
- console.error('Invalid type ' + type + ' for reviewer.');
+ this.reporting.error(new Error(`Invalid type ${type} for reviewer.`));
return;
}
this._reviewersPendingRemove[type].push(account);
@@ -1120,8 +1120,11 @@
if (containsAll(currentAttentionSet, newAttentionSet)) {
return 'No additions to the attention set.';
}
- console.error(
- '_computeDoNotUpdateMessage() should not be called when users were added to the attention set.'
+ this.reporting.error(
+ new Error(
+ '_computeDoNotUpdateMessage()' +
+ 'should not be called when users were added to the attention set.'
+ )
);
return '';
}
@@ -1318,7 +1321,9 @@
this._focusOn(FocusTarget.REVIEWERS);
return;
}
- console.error('_confirmPendingReviewer called without pending confirm');
+ this.reporting.error(
+ new Error('_confirmPendingReviewer called without pending confirm')
+ );
}
_cancelPendingReviewer() {
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
index a1909c8..b39481c 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
@@ -790,7 +790,9 @@
authRedirect?: boolean
) {
if (!this[handlerName]) {
- console.error('Attempted to map route to unknown method: ', handlerName);
+ this.reporting.error(
+ new Error(`Attempted to map route to unknown method: ${handlerName}`)
+ );
return;
}
page(
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
index 83840a5f..b01ac51 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
@@ -125,6 +125,8 @@
private readonly restApiService = appContext.restApiService;
+ reporting = appContext.reportingService;
+
get keyBindings() {
return {
'ctrl+s meta+s': '_handleSaveShortcut',
@@ -330,7 +332,7 @@
this._saveEdit().then(() => {
const handleError: ErrorCallback = response => {
this._showAlert(PUBLISH_FAILED_MSG);
- console.error(response);
+ this.reporting.error(new Error(response?.statusText));
};
this._showAlert(PUBLISHING_EDIT_MSG);
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.ts
index 0cb628c..a493e51 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.ts
@@ -18,6 +18,7 @@
import {GrAnnotation} from '../../diff/gr-diff-highlight/gr-annotation';
import {GrStyleObject} from '../../plugins/gr-styles-api/gr-styles-api';
import {GrDiffLine} from '../../diff/gr-diff/gr-diff-line';
+import {appContext} from '../../../services/app-context';
/**
* Used to create a context for GrAnnotationActionsInterface.
@@ -42,6 +43,8 @@
changeNum: number;
+ private readonly reporting = appContext.reportingService;
+
constructor(
contentEl: HTMLElement,
lineNumberEl: HTMLElement,
@@ -56,8 +59,8 @@
this.path = path;
this.changeNum = Number(changeNum);
if (isNaN(this.changeNum)) {
- console.error(
- `GrAnnotationActionsContext: Invalid changeNum: ${changeNum}`
+ this.reporting.error(
+ new Error(`GrAnnotationActionsContext: Invalid changeNum: ${changeNum}`)
);
}
}
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts
index e069f8b..f160807 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts
@@ -24,6 +24,7 @@
import {Side} from '../../../constants/constants';
import {PluginApi} from '../../plugins/gr-plugin-types';
import {ChangeInfo, NumericChangeId} from '../../../types/common';
+import {appContext} from '../../../services/app-context';
type AddLayerFunc = (ctx: GrAnnotationActionsContext) => void;
@@ -52,6 +53,8 @@
// Default impl is a no-op.
private addLayerFunc: AddLayerFunc = () => {};
+ reporting = appContext.reportingService;
+
constructor(private readonly plugin: PluginApi) {
// Return this instance when there is an annotatediff event.
plugin.on('annotatediff', this);
@@ -134,12 +137,14 @@
) {
this.plugin.hook('annotation-toggler').onAttached(element => {
if (!element.content) {
- console.error('plugin endpoint without content.');
+ this.reporting.error(new Error('plugin endpoint without content.'));
return;
}
if (!element.content.hidden) {
- console.error(
- element.content.id + ' is already enabled. Cannot re-enable.'
+ this.reporting.error(
+ new Error(
+ `${element.content.id} is already enabled. Cannot re-enable.`
+ )
);
return;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.js
index 8b3f501..7ae34cf 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.js
@@ -133,12 +133,9 @@
// Assert that error is shown if we try to enable checkbox again.
onAttachedFuncCalled = false;
annotationActions.enableToggleCheckbox('test label2', onAttachedFunc);
- const errorStub = sinon.stub(
- console, 'error').callsFake((msg, err) => undefined);
+ const errorStub = sinon.stub(annotationActions.reporting, 'error');
emulateAttached();
- assert.isTrue(
- errorStub.calledWith(
- 'annotation-span is already enabled. Cannot re-enable.'));
+ assert.isTrue(errorStub.called);
// Assert that onAttachedFunc is not called and the label has not changed.
assert.isFalse(onAttachedFuncCalled);
assert.equal(document.getElementById('annotation-label').textContent,
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.ts
index 1bcf190..ae6e155 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.ts
@@ -36,6 +36,7 @@
import {EventType, TargetElement} from '../../plugins/gr-plugin-types';
import {DiffLayer, HighlightJS} from '../../../types/types';
import {ParsedChangeInfo} from '../gr-rest-api-interface/gr-reviewer-updates-parser';
+import {appContext} from '../../../services/app-context';
const elements: {[key: string]: HTMLElement} = {};
const eventCallbacks: {[key: string]: EventCallback[]} = {};
@@ -44,6 +45,8 @@
export class GrJsApiInterface
extends GestureEventListeners(LegacyElementMixin(PolymerElement))
implements JsApiService {
+ private readonly reporting = appContext.reportingService;
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
handleEvent(type: EventType, detail: any) {
getPluginLoader()
@@ -99,7 +102,7 @@
try {
return callback(change, revision) === false;
} catch (err) {
- console.error(err);
+ this.reporting.error(err);
}
return false;
});
@@ -120,7 +123,7 @@
try {
cb(detail.path);
} catch (err) {
- console.error(err);
+ this.reporting.error(err);
}
}
}
@@ -161,7 +164,7 @@
try {
cb(change, revision, info);
} catch (err) {
- console.error(err);
+ this.reporting.error(err);
}
}
}
@@ -174,7 +177,7 @@
try {
cb(detail.revisionActions, detail.change);
} catch (err) {
- console.error(err);
+ this.reporting.error(err);
}
}
}
@@ -184,7 +187,7 @@
try {
cb(change, msg);
} catch (err) {
- console.error(err);
+ this.reporting.error(err);
}
}
}
@@ -195,7 +198,7 @@
try {
cb(detail.node);
} catch (err) {
- console.error(err);
+ this.reporting.error(err);
}
}
}
@@ -205,7 +208,7 @@
try {
cb(detail.change);
} catch (err) {
- console.error(err);
+ this.reporting.error(err);
}
}
}
@@ -215,7 +218,7 @@
try {
cb(detail.hljs);
} catch (err) {
- console.error(err);
+ this.reporting.error(err);
}
}
}
@@ -225,7 +228,7 @@
try {
revertMsg = cb(change, revertMsg, origMsg) as string;
} catch (err) {
- console.error(err);
+ this.reporting.error(err);
}
}
return revertMsg;
@@ -244,7 +247,7 @@
origMsg
) as string;
} catch (err) {
- console.error(err);
+ this.reporting.error(err);
}
}
return revertSubmissionMsg;
@@ -258,7 +261,7 @@
const layer = annotationApi.getLayer(path, changeNum);
layers.push(layer);
} catch (err) {
- console.error(err);
+ this.reporting.error(err);
}
}
return layers;
@@ -270,7 +273,7 @@
const annotationApi = (cb as unknown) as GrAnnotationActionsInterface;
annotationApi.disposeLayer(path);
} catch (err) {
- console.error(err);
+ this.reporting.error(err);
}
}
}
@@ -311,7 +314,7 @@
try {
labels = cb(change);
} catch (err) {
- console.error(err);
+ this.reporting.error(err);
}
}
return labels;
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.js
index b5c4e48..1481c2d 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.js
@@ -24,6 +24,7 @@
import {getPluginLoader} from './gr-plugin-loader.js';
import {_testOnly_initGerritPluginApi} from './gr-gerrit.js';
import {stubBaseUrl} from '../../../test/test-utils.js';
+import sinon from 'sinon/pkg/sinon-esm';
const basicFixture = fixtureFromElement('gr-js-api-interface');
@@ -57,7 +58,7 @@
},
});
element = basicFixture.instantiate();
- errorStub = sinon.stub(console, 'error');
+ errorStub = sinon.stub(element.reporting, 'error');
pluginApi.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
getPluginLoader().loadPlugins([]);
@@ -404,6 +405,7 @@
suite('popup', () => {
test('popup(element) is deprecated', () => {
+ sinon.stub(console, 'error');
plugin.popup(document.createElement('div'));
assert.isTrue(console.error.calledOnce);
});
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
index c47f3e2..48c90f7 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
@@ -55,6 +55,7 @@
reportExtension(name: string): void;
pluginLoaded(name: string): void;
pluginsLoaded(pluginsList?: string[]): void;
+ error(err: unknown, reporter?: string, details?: EventDetails): void;
/**
* Reset named timer.
*/
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
index 668834e..b5c0d6d 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
@@ -114,7 +114,22 @@
export function initErrorReporter(appContext: AppContext) {
const reportingService = appContext.reportingService;
- // TODO(dmfilippo): TS-fix-any oldOnError - define correct type
+
+ const normalizeError = (err: Error | unknown) => {
+ if (err instanceof Error) {
+ return err;
+ }
+ let msg = '';
+ if (typeof err === 'string') {
+ msg += err;
+ } else {
+ msg += JSON.stringify(err);
+ }
+ const error = new Error(msg);
+ error.stack = 'unknown';
+ return error;
+ };
+ // TODO(dmfilippov): TS-fix-any oldOnError - define correct type
const onError = function (
oldOnError: Function,
msg: Event | string,
@@ -127,29 +142,15 @@
oldOnError(msg, url, line, column, error);
}
if (error) {
- line = line || error.lineNumber;
- column = column || error.columnNumber;
- let shortenedErrorStack = msg;
- if (error.stack) {
- const errorStackLines = error.stack.split('\n');
- shortenedErrorStack = errorStackLines
- .slice(0, Math.min(3, errorStackLines.length))
- .join('\n');
- }
- msg = shortenedErrorStack || error.toString();
+ line = line ?? error.lineNumber;
+ column = column ?? error.columnNumber;
}
- const payload = {
- url,
+ reportingService.error(normalizeError(error), 'onError', {
line,
column,
- error,
- };
- reportingService.reporter(
- ERROR.TYPE,
- ERROR.CATEGORY.EXCEPTION,
- `${msg}`,
- payload
- );
+ url,
+ msg,
+ });
return true;
};
// TODO(dmfilippov): TS-fix-any unclear what is context
@@ -169,16 +170,7 @@
context.addEventListener(
'unhandledrejection',
(e: PromiseRejectionEvent) => {
- const msg = e.reason.message;
- const payload = {
- error: e.reason,
- };
- reportingService.reporter(
- ERROR.TYPE,
- ERROR.CATEGORY.EXCEPTION,
- msg,
- payload
- );
+ reportingService.error(normalizeError(e.reason), 'unhandledrejection');
}
);
};
@@ -809,6 +801,19 @@
timer.end().reset();
}
+ error(error: Error, errorSource?: string, details?: EventDetails) {
+ const eventDetails = details ?? {};
+ const message = `${errorSource ? errorSource + ': ' : ''}${error.message}`;
+
+ this.reporter(
+ ERROR.TYPE,
+ ERROR.CATEGORY.EXCEPTION,
+ message,
+ {error},
+ {...eventDetails, stack: error.stack}
+ );
+ }
+
reportErrorDialog(message: string) {
this.reporter(
ERROR.TYPE,
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
index 924ddd9..6f75f29 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
@@ -50,6 +50,7 @@
recordDraftInteraction: () => {},
reporter: () => {},
reportErrorDialog: () => {},
+ error: () => {},
reportExtension: () => {},
reportInteraction: () => {},
reportLifeCycle: () => {},
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_test.js b/polygerrit-ui/app/services/gr-reporting/gr-reporting_test.js
index 08e4a55..6e56ab1 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_test.js
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_test.js
@@ -463,23 +463,14 @@
const error = new Error('bar');
error.stack = undefined;
emulateThrow('bar', 'http://url', 4, 2, error);
- assert.isTrue(reporter.calledWith('error', 'exception', 'bar'));
- const payload = reporter.lastCall.args[3];
- assert.deepEqual(payload, {
- url: 'http://url',
- line: 4,
- column: 2,
- error,
- });
+ assert.isTrue(reporter.calledWith('error', 'exception', 'onError: bar'));
});
- test('is reported with 3 lines of stack', () => {
+ test('is reported with stack', () => {
const error = new Error('bar');
emulateThrow('bar', 'http://url', 4, 2, error);
- const expectedStack = error.stack.split('\n').slice(0, 3)
- .join('\n');
- assert.isTrue(reporter.calledWith('error', 'exception',
- expectedStack));
+ const eventDetails = reporter.lastCall.args[4];
+ assert.equal(error.stack, eventDetails.stack);
});
test('prevent default event handler', () => {
@@ -487,12 +478,10 @@
});
test('unhandled rejection', () => {
- fakeWindow.handlers['unhandledrejection']({
- reason: {
- message: 'bar',
- },
- });
- assert.isTrue(reporter.calledWith('error', 'exception', 'bar'));
+ const newError = new Error('bar');
+ fakeWindow.handlers['unhandledrejection']({reason: newError});
+ assert.isTrue(reporter.calledWith('error', 'exception',
+ 'unhandledrejection: bar'));
});
});
});