diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts index 13bd13b804..0145b9f6e4 100644 --- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts +++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts @@ -128,6 +128,7 @@ export class GrRepoCommands extends GestureEventListeners( } _handleRunningGC() { + if (!this.repo) return; this._runningGC = true; return this.restApiService .runRepoGC(this.repo) 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 085aa27f07..63019200f0 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 @@ -106,7 +106,7 @@ import {GrStorage, StorageLocation} from '../../shared/gr-storage/gr-storage'; import {isAttentionSetEnabled} from '../../../utils/attention-set-util'; import {CODE_REVIEW, getMaxAccounts} from '../../../utils/label-util'; import {isUnresolved} from '../../../utils/comment-util'; -import {fireAlert} from '../../../utils/event-util'; +import {fireAlert, fireServerError} from '../../../utils/event-util'; const STORAGE_DEBOUNCE_INTERVAL_MS = 400; @@ -721,13 +721,7 @@ export class GrReplyDialog extends KeyboardShortcutMixin( return new Map(); } if (!response.ok) { - this.dispatchEvent( - new CustomEvent('server-error', { - detail: {response}, - composed: true, - bubbles: true, - }) - ); + fireServerError(response); return new Map(); } @@ -790,8 +784,9 @@ export class GrReplyDialog extends KeyboardShortcutMixin( return account._account_id === change.owner._account_id; } - _handle400Error(response?: Response | null) { - if (!response) throw new Error('Reponse is empty.'); + _handle400Error(r?: Response | null) { + if (!r) throw new Error('Reponse is empty.'); + let response: Response = r; // A call to _saveReview could fail with a server error if erroneous // reviewers were requested. This is signalled with a 400 Bad Request // status. The default gr-rest-api-interface error handling would @@ -813,7 +808,7 @@ export class GrReplyDialog extends KeyboardShortcutMixin( const result = parsed as ReviewResult; // Only perform custom error handling for 400s and a parseable // ReviewResult response. - if (response && response.status === 400 && result && result.reviewers) { + if (response.status === 400 && result && result.reviewers) { const errors: string[] = []; const addReviewers = Object.values(result.reviewers); addReviewers.forEach(r => errors.push(r.error ?? 'no explanation')); @@ -823,13 +818,7 @@ export class GrReplyDialog extends KeyboardShortcutMixin( text: () => Promise.resolve(errors.join(', ')), }; } - this.dispatchEvent( - new CustomEvent('server-error', { - detail: {response}, - composed: true, - bubbles: true, - }) - ); + fireServerError(response); }); } diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.js index a60662486b..3379cd4472 100644 --- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.js +++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.js @@ -21,6 +21,7 @@ import './gr-reply-dialog.js'; import {mockPromise} from '../../../test/test-utils.js'; import {SpecialFilePath} from '../../../constants/constants.js'; import {appContext} from '../../../services/app-context.js'; +import {addListenerForTest} from '../../../test/test-utils.js'; const basicFixture = fixtureFromElement('gr-reply-dialog'); @@ -778,23 +779,23 @@ suite('gr-reply-dialog tests', () => { assert.isTrue(eraseDraftCommentStub.calledWith(location)); }); - test('400 converts to human-readable server-error', async () => { + test('400 converts to human-readable server-error', done => { sinon.stub(window, 'fetch').callsFake(() => { const text = '....{"reviewers":{"id1":{"error":"human readable"}}}'; return Promise.resolve(cloneableResponse(400, text)); }); - let resolver; - const promise = new Promise(r => resolver = r); - element.addEventListener('server-error', resolver); + const listener = event => { + if (event.target !== document) return; + event.detail.response.text().then(body => { + if (body === 'human readable') { + done(); + } + }); + }; + addListenerForTest(document, 'server-error', listener); - await flush(); - element.send(); - - const event = await promise; - assert.equal(event.target, element); - const text = await event.detail.response.text(); - assert.equal(text, 'human readable'); + flush(() => { element.send(); }); }); test('non-json 400 is treated as a normal server-error', done => { @@ -803,15 +804,15 @@ suite('gr-reply-dialog tests', () => { return Promise.resolve(cloneableResponse(400, text)); }); - element.addEventListener('server-error', event => { - if (event.target !== element) { - return; - } + const listener = event => { + if (event.target !== document) return; event.detail.response.text().then(body => { - assert.equal(body, 'Comment validation error!'); - done(); + if (body === 'Comment validation error!') { + done(); + } }); - }); + }; + addListenerForTest(document, 'server-error', listener); // Async tick is needed because iron-selector content is distributed and // distributed content requires an observer to be set up. @@ -1255,12 +1256,13 @@ suite('gr-reply-dialog tests', () => { }, }, }); - element.addEventListener('server-error', e => { + const listener = e => { e.detail.response.text().then(text => { assert.equal(text, [error1, error2, error3].join(', ')); done(); }); - }); + }; + addListenerForTest(document, 'server-error', listener); element._handle400Error(cloneableResponse(400, text)); }); diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.ts b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.ts index 4aa239a0a3..e868f03a40 100644 --- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.ts +++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.ts @@ -34,10 +34,10 @@ import {EventEmitterService} from '../../../services/gr-event-interface/gr-event import {GrOverlay} from '../../shared/gr-overlay/gr-overlay'; import {GrErrorDialog} from '../gr-error-dialog/gr-error-dialog'; import {GrAlert} from '../../shared/gr-alert/gr-alert'; -import {FetchRequest} from '../../shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper'; import {ErrorType, FixIronA11yAnnouncer} from '../../../types/types'; import {AccountId} from '../../../types/common'; import {EventType} from '../../../utils/event-util'; +import {NetworkErrorEvent, ServerErrorEvent} from '../../../types/events'; const HIDE_ALERT_TIMEOUT_MS = 5000; const CHECK_SIGN_IN_INTERVAL_MS = 60 * 1000; @@ -125,8 +125,8 @@ export class GrErrorManager extends GestureEventListeners( /** @override */ attached() { super.attached(); - this.listen(document, 'server-error', '_handleServerError'); - this.listen(document, 'network-error', '_handleNetworkError'); + this.listen(document, EventType.SERVER_ERROR, '_handleServerError'); + this.listen(document, EventType.NETWORK_ERROR, '_handleNetworkError'); this.listen(document, EventType.SHOW_ALERT, '_handleShowAlert'); this.listen(document, 'hide-alert', '_hideAlert'); this.listen(document, 'show-error', '_handleShowErrorDialog'); @@ -147,8 +147,8 @@ export class GrErrorManager extends GestureEventListeners( detached() { super.detached(); this._clearHideAlertHandle(); - this.unlisten(document, 'server-error', '_handleServerError'); - this.unlisten(document, 'network-error', '_handleNetworkError'); + this.unlisten(document, EventType.SERVER_ERROR, '_handleServerError'); + this.unlisten(document, EventType.NETWORK_ERROR, '_handleNetworkError'); this.unlisten(document, EventType.SHOW_ALERT, '_handleShowAlert'); this.unlisten(document, 'hide-alert', '_hideAlert'); this.unlisten(document, 'show-error', '_handleShowErrorDialog'); @@ -177,9 +177,7 @@ export class GrErrorManager extends GestureEventListeners( }); } - _handleServerError( - e: CustomEvent<{response: Response; request: FetchRequest}> - ) { + _handleServerError(e: ServerErrorEvent) { const {request, response} = e.detail; response.text().then(errorText => { const url = request && (request.anonymizedUrl || request.url); @@ -296,7 +294,7 @@ export class GrErrorManager extends GestureEventListeners( ); } - _handleNetworkError(e: CustomEvent) { + _handleNetworkError(e: NetworkErrorEvent) { this._showAlert('Server unavailable'); console.error(e.detail.error.message); } diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts index a267b0b427..3c401d018e 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts @@ -68,7 +68,11 @@ import {FilesWebLinks} from '../gr-patch-range-select/gr-patch-range-select'; import {LineNumber, FILE} from '../gr-diff/gr-diff-line'; import {GrCommentThread} from '../../shared/gr-comment-thread/gr-comment-thread'; import {KnownExperimentId} from '../../../services/flags/flags'; -import {firePageError, fireAlert} from '../../../utils/event-util'; +import { + firePageError, + fireAlert, + fireServerError, +} from '../../../utils/event-util'; const MSG_EMPTY_BLAME = 'No blame information for this diff.'; @@ -591,13 +595,7 @@ export class GrDiffHost extends GestureEventListeners( // Loading the diff may respond with 409 if the file is too large. In this // case, use a toast error.. if (response.status === 409) { - this.dispatchEvent( - new CustomEvent('server-error', { - detail: {response}, - composed: true, - bubbles: true, - }) - ); + fireServerError(response); return; } diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js index 81a3604b40..9bde12243b 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js +++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js @@ -304,11 +304,15 @@ suite('gr-diff-host tests', () => { setup(() => { serverErrorStub = sinon.stub(); - element.addEventListener('server-error', serverErrorStub); + document.addEventListener('server-error', serverErrorStub); pageErrorStub = sinon.stub(); element.addEventListener('page-error', pageErrorStub); }); + teardown(() => { + document.removeEventListener('server-error', serverErrorStub); + }); + test('page error on HTTP-409', () => { element._handleGetDiffError({status: 409}); assert.isTrue(serverErrorStub.calledOnce); diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts index edb0113360..1c6e0b605f 100644 --- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts +++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts @@ -157,7 +157,7 @@ import { HttpMethod, ReviewerState, } from '../../../constants/constants'; -import {firePageError} from '../../../utils/event-util'; +import {firePageError, fireServerError} from '../../../utils/event-util'; const JSON_PREFIX = ")]}'"; const MAX_PROJECT_RESULTS = 25; @@ -408,19 +408,7 @@ export class GrRestApiInterface }) as Promise; } - saveRepoConfig(repo: RepoName, config: ConfigInput): Promise; - - saveRepoConfig( - repo: RepoName, - config: ConfigInput, - errFn: ErrorCallback - ): Promise; - - saveRepoConfig( - repo: RepoName, - config: ConfigInput, - errFn?: ErrorCallback - ): Promise { + saveRepoConfig(repo: RepoName, config: ConfigInput): Promise { // TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend // supports it. const url = `/projects/${encodeURIComponent(repo)}/config`; @@ -429,23 +417,11 @@ export class GrRestApiInterface method: HttpMethod.PUT, url, body: config, - errFn, anonymizedUrl: '/projects/*/config', }); } - runRepoGC(repo: RepoName): Promise; - - runRepoGC( - repo: RepoName, - errFn: ErrorCallback - ): Promise; - - runRepoGC(repo: RepoName, errFn?: ErrorCallback) { - if (!repo) { - // TODO(TS): fix return value - return ''; - } + runRepoGC(repo: RepoName): Promise { // TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend // supports it. const encodeName = encodeURIComponent(repo); @@ -453,23 +429,11 @@ export class GrRestApiInterface method: HttpMethod.POST, url: `/projects/${encodeName}/gc`, body: '', - errFn, anonymizedUrl: '/projects/*/gc', }); } - createRepo(config: ProjectInput & {name: RepoName}): Promise; - - createRepo( - config: ProjectInput & {name: RepoName}, - errFn: ErrorCallback - ): Promise; - - createRepo(config: ProjectInput, errFn?: ErrorCallback) { - if (!config.name) { - // TODO(TS): Fix return value - return ''; - } + createRepo(config: ProjectInput & {name: RepoName}): Promise { // TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend // supports it. const encodeName = encodeURIComponent(config.name); @@ -477,29 +441,16 @@ export class GrRestApiInterface method: HttpMethod.PUT, url: `/projects/${encodeName}`, body: config, - errFn, anonymizedUrl: '/projects/*', }); } - createGroup(config: GroupInput & {name: string}): Promise; - - createGroup( - config: GroupInput & {name: string}, - errFn: ErrorCallback - ): Promise; - - createGroup(config: GroupInput, errFn?: ErrorCallback) { - if (!config.name) { - // TODO(TS): Fix return value - return ''; - } + createGroup(config: GroupInput & {name: string}): Promise { const encodeName = encodeURIComponent(config.name); return this._restApiHelper.send({ method: HttpMethod.PUT, url: `/groups/${encodeName}`, body: config, - errFn, anonymizedUrl: '/groups/*', }); } @@ -515,19 +466,7 @@ export class GrRestApiInterface }) as Promise; } - deleteRepoBranches(repo: RepoName, ref: GitRef): Promise; - - deleteRepoBranches( - repo: RepoName, - ref: GitRef, - errFn: ErrorCallback - ): Promise; - - deleteRepoBranches(repo: RepoName, ref: GitRef, errFn?: ErrorCallback) { - if (!repo || !ref) { - // TODO(TS): fix return value - return ''; - } + deleteRepoBranches(repo: RepoName, ref: GitRef): Promise { // TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend // supports it. const encodeName = encodeURIComponent(repo); @@ -536,24 +475,11 @@ export class GrRestApiInterface method: HttpMethod.DELETE, url: `/projects/${encodeName}/branches/${encodeRef}`, body: '', - errFn, anonymizedUrl: '/projects/*/branches/*', }); } - deleteRepoTags(repo: RepoName, ref: GitRef): Promise; - - deleteRepoTags( - repo: RepoName, - ref: GitRef, - errFn: ErrorCallback - ): Promise; - - deleteRepoTags(repo: RepoName, ref: GitRef, errFn?: ErrorCallback) { - if (!repo || !ref) { - // TODO(TS): fix return type - return ''; - } + deleteRepoTags(repo: RepoName, ref: GitRef): Promise { // TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend // supports it. const encodeName = encodeURIComponent(repo); @@ -562,7 +488,6 @@ export class GrRestApiInterface method: HttpMethod.DELETE, url: `/projects/${encodeName}/tags/${encodeRef}`, body: '', - errFn, anonymizedUrl: '/projects/*/tags/*', }); } @@ -571,25 +496,7 @@ export class GrRestApiInterface name: RepoName, branch: BranchName, revision: BranchInput - ): Promise; - - createRepoBranch( - name: RepoName, - branch: BranchName, - revision: BranchInput, - errFn: ErrorCallback - ): Promise; - - createRepoBranch( - name: RepoName, - branch: BranchName, - revision: BranchInput, - errFn?: ErrorCallback - ) { - if (!name || !branch || !revision) { - // TODO(TS) fix return type - return ''; - } + ): Promise { // TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend // supports it. const encodeName = encodeURIComponent(name); @@ -598,7 +505,6 @@ export class GrRestApiInterface method: HttpMethod.PUT, url: `/projects/${encodeName}/branches/${encodeBranch}`, body: revision, - errFn, anonymizedUrl: '/projects/*/branches/*', }); } @@ -607,25 +513,7 @@ export class GrRestApiInterface name: RepoName, tag: string, revision: TagInput - ): Promise; - - createRepoTag( - name: RepoName, - tag: string, - revision: TagInput, - errFn: ErrorCallback - ): Promise; - - createRepoTag( - name: RepoName, - tag: string, - revision: TagInput, - errFn?: ErrorCallback - ) { - if (!name || !tag || !revision) { - // TODO(TS): Fix return value - return ''; - } + ): Promise { // TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend // supports it. const encodeName = encodeURIComponent(name); @@ -634,7 +522,6 @@ export class GrRestApiInterface method: HttpMethod.PUT, url: `/projects/${encodeName}/tags/${encodeTag}`, body: revision, - errFn, anonymizedUrl: '/projects/*/tags/*', }); } @@ -650,16 +537,12 @@ export class GrRestApiInterface ); } - getGroupMembers( - groupName: GroupId | GroupName, - errFn?: ErrorCallback - ): Promise { + getGroupMembers(groupName: GroupId | GroupName): Promise { const encodeName = encodeURIComponent(groupName); - return this._restApiHelper.fetchJSON({ + return (this._restApiHelper.fetchJSON({ url: `/groups/${encodeName}/members/`, - errFn, anonymizedUrl: '/groups/*/members', - }) as Promise; + }) as unknown) as Promise; } getIncludedGroup( @@ -865,14 +748,7 @@ export class GrRestApiInterface }); } - savePreferences(prefs: PreferencesInput): Promise; - - savePreferences( - prefs: PreferencesInput, - errFn: ErrorCallback - ): Promise; - - savePreferences(prefs: PreferencesInput, errFn?: ErrorCallback) { + savePreferences(prefs: PreferencesInput): Promise { // Note (Issue 5142): normalize the download scheme with lower case before // saving. if (prefs.download_scheme) { @@ -883,45 +759,28 @@ export class GrRestApiInterface method: HttpMethod.PUT, url: '/accounts/self/preferences', body: prefs, - errFn, reportUrlAsIs: true, }); } - saveDiffPreferences(prefs: DiffPreferenceInput): Promise; - - saveDiffPreferences( - prefs: DiffPreferenceInput, - errFn: ErrorCallback - ): Promise; - - saveDiffPreferences(prefs: DiffPreferenceInput, errFn?: ErrorCallback) { + saveDiffPreferences(prefs: DiffPreferenceInput): Promise { // Invalidate the cache. this._cache.delete('/accounts/self/preferences.diff'); return this._restApiHelper.send({ method: HttpMethod.PUT, url: '/accounts/self/preferences.diff', body: prefs, - errFn, reportUrlAsIs: true, }); } - saveEditPreferences(prefs: EditPreferencesInfo): Promise; - - saveEditPreferences( - prefs: EditPreferencesInfo, - errFn: ErrorCallback - ): Promise; - - saveEditPreferences(prefs: EditPreferencesInfo, errFn?: ErrorCallback) { + saveEditPreferences(prefs: EditPreferencesInfo): Promise { // Invalidate the cache. this._cache.delete('/accounts/self/preferences.edit'); return this._restApiHelper.send({ method: HttpMethod.PUT, url: '/accounts/self/preferences.edit', body: prefs, - errFn, reportUrlAsIs: true, }); } @@ -1494,13 +1353,7 @@ export class GrRestApiInterface if (errFn) { errFn.call(null, response); } else { - document.dispatchEvent( - new CustomEvent('server-error', { - detail: {request: req, response}, - composed: true, - bubbles: true, - }) - ); + fireServerError(response, req); } return undefined; } @@ -2207,14 +2060,8 @@ export class GrRestApiInterface // 404s indicate the file does not exist yet in the revision, so suppress // them. const suppress404s: ErrorCallback = res => { - if (res?.status !== 404) { - document.dispatchEvent( - new CustomEvent('server-error', { - detail: {res}, - composed: true, - bubbles: true, - }) - ); + if (res && res?.status !== 404) { + fireServerError(res); } return res; }; diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.js index bd2818cb07..4bc7dd3c54 100644 --- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.js +++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.js @@ -1294,7 +1294,7 @@ suite('gr-rest-api-interface tests', () => { }) .then(() => { assert.isTrue(spy.called); - assert.notEqual(spy.lastCall.args[0].detail.res.status, 404); + assert.notEqual(spy.lastCall.args[0].detail.response.status, 404); }); }); diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.ts b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.ts index 8aa1b0f13a..43a6af4d0a 100644 --- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.ts +++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.ts @@ -32,6 +32,8 @@ import { } from '../../../../types/common'; import {HttpMethod} from '../../../../constants/constants'; import {RpcLogEventDetail} from '../../../../types/events'; +import {fireNetworkError, fireServerError} from '../../../../utils/event-util'; +import {FetchRequest} from '../../../../types/types'; const JSON_PREFIX = ")]}'"; @@ -188,12 +190,6 @@ export interface SendJSONRequest extends SendRequestBase { export type SendRequest = SendRawRequest | SendJSONRequest; -export interface FetchRequest { - url: string; - fetchOptions?: AuthRequestInit; - anonymizedUrl?: string; -} - export interface FetchJSONRequest extends FetchRequest { reportUrlAsIs?: boolean; params?: FetchParams; @@ -315,13 +311,7 @@ s */ if (req.errFn) { req.errFn.call(undefined, null, err); } else { - document.dispatchEvent( - new CustomEvent('network-error', { - detail: {error: err}, - composed: true, - bubbles: true, - }) - ); + fireNetworkError(err); } throw err; }); @@ -350,13 +340,7 @@ s */ req.errFn.call(null, response); return; } - document.dispatchEvent( - new CustomEvent('server-error', { - detail: {request: req, response}, - composed: true, - bubbles: true, - }) - ); + fireServerError(response, req); return; } return this.getResponseObject(response); @@ -510,13 +494,7 @@ s */ }; const xhr = this.fetch(fetchReq) .catch(err => { - document.dispatchEvent( - new CustomEvent('network-error', { - detail: {error: err}, - composed: true, - bubbles: true, - }) - ); + fireNetworkError(err); if (req.errFn) { return req.errFn.call(undefined, null, err); } else { @@ -529,13 +507,7 @@ s */ req.errFn.call(undefined, response); return; } - document.dispatchEvent( - new CustomEvent('server-error', { - detail: {request: fetchReq, response}, - composed: true, - bubbles: true, - }) - ); + fireServerError(response, fetchReq); } return response; }); diff --git a/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts b/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts index cf064c1148..8a0f91279e 100644 --- a/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts +++ b/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts @@ -232,26 +232,10 @@ export interface RestApiService { getDiffPreferences(): Promise; saveDiffPreferences(prefs: DiffPreferenceInput): Promise; - saveDiffPreferences( - prefs: DiffPreferenceInput, - errFn: ErrorCallback - ): Promise; - saveDiffPreferences( - prefs: DiffPreferenceInput, - errFn?: ErrorCallback - ): Promise; getEditPreferences(): Promise; saveEditPreferences(prefs: EditPreferencesInfo): Promise; - saveEditPreferences( - prefs: EditPreferencesInfo, - errFn: ErrorCallback - ): Promise; - saveEditPreferences( - prefs: EditPreferencesInfo, - errFn?: ErrorCallback - ): Promise; getAccountEmails(): Promise; deleteAccountEmail(email: string): Promise; @@ -267,25 +251,11 @@ export interface RestApiService { revision: BranchInput ): Promise; - createRepoBranch( - name: RepoName, - branch: BranchName, - revision: BranchInput, - errFn: ErrorCallback - ): Promise; - createRepoTag( name: RepoName, tag: string, revision: TagInput ): Promise; - - createRepoTag( - name: RepoName, - tag: string, - revision: TagInput, - errFn: ErrorCallback - ): Promise; addAccountGPGKey(key: GpgKeysInput): Promise>; deleteAccountGPGKey(id: GpgKeyId): Promise; getAccountGPGKeys(): Promise>; @@ -325,11 +295,6 @@ export interface RestApiService { ): Promise; createRepo(config: ProjectInput & {name: RepoName}): Promise; - createRepo( - config: ProjectInput & {name: RepoName}, - errFn: ErrorCallback - ): Promise; - createRepo(config: ProjectInput, errFn?: ErrorCallback): Promise; getRepo( repo: RepoName, @@ -522,11 +487,6 @@ export interface RestApiService { | Promise; createGroup(config: GroupInput & {name: string}): Promise; - createGroup( - config: GroupInput & {name: string}, - errFn: ErrorCallback - ): Promise; - createGroup(config: GroupInput, errFn?: ErrorCallback): Promise; getPlugins( filter: string, @@ -660,10 +620,7 @@ export interface RestApiService { errFn?: ErrorCallback ): Promise; - getGroupMembers( - groupName: GroupId | GroupName, - errFn?: ErrorCallback - ): Promise; + getGroupMembers(groupName: GroupId | GroupName): Promise; getIncludedGroup( groupName: GroupId | GroupName @@ -690,10 +647,7 @@ export interface RestApiService { includedGroup: GroupId ): Promise; - runRepoGC( - repo: RepoName, - errFn?: ErrorCallback - ): Promise; + runRepoGC(repo: RepoName): Promise; getFileContent( changeNum: NumericChangeId, path: string, diff --git a/polygerrit-ui/app/test/test-utils.ts b/polygerrit-ui/app/test/test-utils.ts index cccba5d5a9..08642af97e 100644 --- a/polygerrit-ui/app/test/test-utils.ts +++ b/polygerrit-ui/app/test/test-utils.ts @@ -105,6 +105,25 @@ export function registerTestCleanup(cleanupCallback: CleanupCallback) { cleanups.push(cleanupCallback); } +export function addListenerForTest( + el: EventTarget, + type: string, + listener: EventListenerOrEventListenerObject +) { + el.addEventListener(type, listener); + registerListenerCleanup(el, type, listener); +} + +export function registerListenerCleanup( + el: EventTarget, + type: string, + listener: EventListenerOrEventListenerObject +) { + registerTestCleanup(() => { + el.removeEventListener(type, listener); + }); +} + export function cleanupTestUtils() { cleanups.forEach(cleanup => cleanup()); cleanups.splice(0); diff --git a/polygerrit-ui/app/types/events.ts b/polygerrit-ui/app/types/events.ts index f136045669..e0b3348c5e 100644 --- a/polygerrit-ui/app/types/events.ts +++ b/polygerrit-ui/app/types/events.ts @@ -19,6 +19,7 @@ import {PatchSetNum} from './common'; import {UIComment} from '../utils/comment-util'; import {Side} from '../constants/constants'; import {LineNumber} from '../elements/diff/gr-diff/gr-diff-line'; +import {FetchRequest} from './types'; export interface TitleChangeEventDetail { title: string; @@ -44,6 +45,31 @@ declare global { } } +export interface ServerErrorEventDetail { + request?: FetchRequest; + response: Response; +} + +export type ServerErrorEvent = CustomEvent; + +declare global { + interface DocumentEventMap { + 'server-error': ServerErrorEvent; + } +} + +export interface NetworkErrorEventDetail { + error: Error; +} + +export type NetworkErrorEvent = CustomEvent; + +declare global { + interface DocumentEventMap { + 'network-error': NetworkErrorEvent; + } +} + export interface LocationChangeEventDetail { hash: string; pathname: string; diff --git a/polygerrit-ui/app/types/types.ts b/polygerrit-ui/app/types/types.ts index b40d618e47..232e204104 100644 --- a/polygerrit-ui/app/types/types.ts +++ b/polygerrit-ui/app/types/types.ts @@ -27,6 +27,7 @@ import { PatchSetNum, } from './common'; import {PolymerSpliceChange} from '@polymer/polymer/interfaces'; +import {AuthRequestInit} from '../services/gr-auth/gr-auth'; export function notUndefined(x: T | undefined): x is T { return x !== undefined; @@ -237,3 +238,9 @@ export function isPolymerSpliceChange< >(x: T | PolymerSpliceChange): x is PolymerSpliceChange { return (x as PolymerSpliceChange).indexSplices !== undefined; } + +export interface FetchRequest { + url: string; + fetchOptions?: AuthRequestInit; + anonymizedUrl?: string; +} diff --git a/polygerrit-ui/app/utils/event-util.ts b/polygerrit-ui/app/utils/event-util.ts index 0af8fe2b0c..36341bd493 100644 --- a/polygerrit-ui/app/utils/event-util.ts +++ b/polygerrit-ui/app/utils/event-util.ts @@ -15,9 +15,13 @@ * limitations under the License. */ +import {FetchRequest} from '../types/types'; + export enum EventType { SHOW_ALERT = 'show-alert', PAGE_ERROR = 'page-error', + SERVER_ERROR = 'server-error', + NETWORK_ERROR = 'network-error', TITLE_CHANGE = 'title-change', } @@ -41,6 +45,26 @@ export function firePageError(target: EventTarget, response?: Response | null) { ); } +export function fireServerError(response: Response, request?: FetchRequest) { + document.dispatchEvent( + new CustomEvent(EventType.SERVER_ERROR, { + detail: {response, request}, + composed: true, + bubbles: true, + }) + ); +} + +export function fireNetworkError(error: Error) { + document.dispatchEvent( + new CustomEvent(EventType.NETWORK_ERROR, { + detail: {error}, + composed: true, + bubbles: true, + }) + ); +} + export function fireTitleChange(target: EventTarget, title: string) { target.dispatchEvent( new CustomEvent(EventType.TITLE_CHANGE, {