Merge "Revert "ReceiveCommits: Retry inserting changes and patch sets""
diff --git a/Documentation/images/user-attention-set-dashboard-empty.png b/Documentation/images/user-attention-set-dashboard-empty.png
new file mode 100644
index 0000000..7b15fa0
--- /dev/null
+++ b/Documentation/images/user-attention-set-dashboard-empty.png
Binary files differ
diff --git a/Documentation/images/user-attention-set-hovercard.png b/Documentation/images/user-attention-set-hovercard.png
index b5638fd..8d6af58 100644
--- a/Documentation/images/user-attention-set-hovercard.png
+++ b/Documentation/images/user-attention-set-hovercard.png
Binary files differ
diff --git a/Documentation/images/user-attention-set-icon-click.png b/Documentation/images/user-attention-set-icon-click.png
new file mode 100644
index 0000000..32b1961
--- /dev/null
+++ b/Documentation/images/user-attention-set-icon-click.png
Binary files differ
diff --git a/Documentation/images/user-attention-set-reply-modify.png b/Documentation/images/user-attention-set-reply-modify.png
index cc12753..7705d14 100644
--- a/Documentation/images/user-attention-set-reply-modify.png
+++ b/Documentation/images/user-attention-set-reply-modify.png
Binary files differ
diff --git a/Documentation/images/user-attention-set-reply-select.png b/Documentation/images/user-attention-set-reply-select.png
index 14b2967..14fadfe3 100644
--- a/Documentation/images/user-attention-set-reply-select.png
+++ b/Documentation/images/user-attention-set-reply-select.png
Binary files differ
diff --git a/Documentation/user-attention-set.txt b/Documentation/user-attention-set.txt
index 11d1bc9..f870405 100644
--- a/Documentation/user-attention-set.txt
+++ b/Documentation/user-attention-set.txt
@@ -7,6 +7,8 @@
Report a bug or send feedback using
link:https://bugs.chromium.org/p/gerrit/issues/entry?template=Attention+Set[this Monorail template].
+You can also report a bug through the bug icon in the user hovercard and in the
+reply dialog.
[[whose-turn]]
== Whose turn is it?
@@ -46,20 +48,26 @@
conversations that the user is replying to.
* If a *reviewer* replies, then the change owner (and uploader) are added to the
attention set.
+* For merged and abandoned changes the owner is added when a new human comment
+ is created.
* Only owner, uploader, reviewers and ccs can be in the attention set.
*!IMPORTANT!* These rules are not meant to be super smart and to always do the
right thing, e.g. if the change owner sends a reply, then they are often
-expected to individually select whose turn it is instead of adding *all*
-reviewers to the attention set.
+expected to individually select whose turn it is.
Note that just uploading a new patchset is not a relevant event for the
attention set to change.
=== Interaction
-There are two ways to interact with the attention set: The hovercard of owner
-and reviewer chips and the "Reply" dialog.
+There are three ways to interact with the attention set: The attention icon,
+the hovercard of owner and reviewer chips and the "Reply" dialog.
+
+*The attention icon* can be used to quickly remove yourself (or someone else)
+from the attention set. Just click the icon, and it will disappear:
+
+image::images/user-attention-set-icon-click.png["attention set icon with tooltip", align="center"]
*The hovercard* (on both the Dashboard and Change page) contains information
about whether, why and when a user was added to the attention set. It also
@@ -72,8 +80,7 @@
image::images/user-attention-set-reply-modify.png["reply dialog section for modifying", align="center"]
-If you do not click "MODIFY", then the backend will just apply the
-automated rules as stated above. If you click "MODIFY", then the section will
+If you click "MODIFY", then the section will
expand and you can select and de-select users by clicking on their chips.
Whatever you select here will be the new state of the attention set for this
change. As a change owner make sure to remove reviewers that you don't expect to
@@ -83,23 +90,24 @@
=== Bots
-[Caveat: This is not fully implemented yet!]
-
The attention set is meant for human reviews only. Triggering bots and reacting
to their results is a different workflow and not in scope of the attenion set.
Thus members of the "Service Users" group will never be added to the
-attention set. And replies by such users will not add the change owner to the
-attention set.
+attention set. And replies by such users will only add the change owner (and
+uploader) to the attention set, if it comes along with a negative vote.
=== Dashboard
The default *dashboard* contains a new section at the top called "Your Turn". It
-lists all changes where the logged-in user is in the attention set. As an active
-developer one of your daily goals will be to iterate over this list and clear
-it.
+lists all changes where the logged-in user is in the attention set.
image::images/user-attention-set-dashboard.png["dashboard with Your Turn section", align="center"]
+As an active developer one of your daily goals will be to iterate over this list
+and clear it.
+
+image::images/user-attention-set-dashboard-empty.png["dashboard with empty Your Turn section", align="center"]
+
Note that you can also navigate to other users' dashboards to check their
"Your Turn" section.
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index 1d08223..87bc9a0 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -49,6 +49,7 @@
listChangesOptionsToHex,
} from '../../../utils/change-util.js';
import {NotifyType} from '../../../constants/constants.js';
+import {TargetElement, EventType} from '../../plugins/gr-plugin-types.js';
const ERR_BRANCH_EMPTY = 'The destination branch can’t be empty.';
const ERR_COMMIT_EMPTY = 'The commit message can’t be empty.';
@@ -495,7 +496,7 @@
/** @override */
ready() {
super.ready();
- this.$.jsAPI.addElement(this.$.jsAPI.Element.CHANGE_ACTIONS, this);
+ this.$.jsAPI.addElement(TargetElement.CHANGE_ACTIONS, this);
this.$.restAPI.getConfig().then(config => {
this._config = config;
});
@@ -555,7 +556,7 @@
_sendShowRevisionActions(detail) {
this.$.jsAPI.handleEvent(
- this.$.jsAPI.EventType.SHOW_REVISION_ACTIONS,
+ EventType.SHOW_REVISION_ACTIONS,
detail
);
}
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 1094809..b1b5a9b 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -58,7 +58,6 @@
import {getPluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints.js';
import {pluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
import {RevisionInfo} from '../../shared/revision-info/revision-info.js';
-
import {PrimaryTab, SecondaryTab} from '../../../constants/constants.js';
import {NO_ROBOT_COMMENTS_THREADS_MSG} from '../../../constants/messages.js';
import {appContext} from '../../../services/app-context.js';
@@ -73,6 +72,7 @@
SPECIAL_PATCH_SET_NUM,
} from '../../../utils/patch-set-util.js';
import {changeStatuses, changeStatusString} from '../../../utils/change-util.js';
+import {EventType} from '../../plugins/gr-plugin-types.js';
const CHANGE_ID_ERROR = {
MISMATCH: 'mismatch',
@@ -1098,7 +1098,7 @@
}
_sendShowChangeEvent() {
- this.$.jsAPI.handleEvent(this.$.jsAPI.EventType.SHOW_CHANGE, {
+ this.$.jsAPI.handleEvent(EventType.SHOW_CHANGE, {
change: this._change,
patchNum: this._patchRange.patchNum,
info: {mergeable: this._mergeable},
@@ -1569,7 +1569,7 @@
this._handleLabelRemoved(changeRecord.value.indexSplices,
changeRecord.path);
}
- this.$.jsAPI.handleEvent(this.$.jsAPI.EventType.LABEL_CHANGE, {
+ this.$.jsAPI.handleEvent(EventType.LABEL_CHANGE, {
change: this._change,
});
}
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.js
index a66c6a2..3975be7 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.js
@@ -27,6 +27,7 @@
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
import {pluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
+import {EventType} from '../../plugins/gr-plugin-types.js';
import 'lodash/lodash.js';
import {generateChange, TestKeyboardShortcutBinder} from '../../../test/test-utils.js';
@@ -2254,8 +2255,7 @@
const showStub = sinon.stub(element.$.jsAPI, 'handleEvent');
element._sendShowChangeEvent();
assert.isTrue(showStub.calledOnce);
- assert.equal(
- showStub.lastCall.args[0], element.$.jsAPI.EventType.SHOW_CHANGE);
+ assert.equal(showStub.lastCall.args[0], EventType.SHOW_CHANGE);
assert.deepEqual(showStub.lastCall.args[1], {
change: {labels: {}},
patchNum: 4,
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index 4ab66d8..c1ab028 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -41,6 +41,7 @@
import {removeServiceUsers} from '../../../utils/account-util.js';
import {getDisplayName} from '../../../utils/display-name-util.js';
import {IronA11yAnnouncer} from '@polymer/iron-a11y-announcer/iron-a11y-announcer.js';
+import {TargetElement} from '../../plugins/gr-plugin-types.js';
const STORAGE_DEBOUNCE_INTERVAL_MS = 400;
@@ -361,7 +362,7 @@
super.ready();
this._isPatchsetCommentsExperimentEnabled = this.flagsService
.isEnabled(KnownExperimentId.PATCHSET_COMMENTS);
- this.$.jsAPI.addElement(this.$.jsAPI.Element.REPLY_DIALOG, this);
+ this.$.jsAPI.addElement(TargetElement.REPLY_DIALOG, this);
}
open(opt_focusTarget) {
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.js b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.js
index 6d8be66..f997e3a 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.js
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.js
@@ -19,6 +19,7 @@
import './gr-edit-controls.js';
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
+import {createIronOverlayBackdropStyleEl} from '../../../test/test-utils.js';
const basicFixture = fixtureFromElement('gr-edit-controls');
@@ -31,14 +32,7 @@
let ironOverlayBackdropStyleEl;
setup(() => {
- // Forcing an opacity of 0 onto the ironOverlayBackdrop is required, because
- // otherwise the backdrop stays around in the DOM for too long waiting for
- // an animation to finish.
- ironOverlayBackdropStyleEl = document.createElement('style');
- document.head.appendChild(ironOverlayBackdropStyleEl);
- ironOverlayBackdropStyleEl.sheet.insertRule(
- 'body { --iron-overlay-backdrop-opacity: 0; }');
-
+ ironOverlayBackdropStyleEl = createIronOverlayBackdropStyleEl();
element = basicFixture.instantiate();
element.change = {_number: '42'};
showDialogSpy = sinon.spy(element, '_showDialog');
diff --git a/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api.ts b/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api.ts
index 51ff0ab..1332118 100644
--- a/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api.ts
@@ -15,6 +15,8 @@
* limitations under the License.
*/
+import {PluginApi} from '../gr-plugin-types';
+
/** Interface for menu link */
export interface MenuLink {
text: string;
@@ -22,11 +24,6 @@
capability: string | null;
}
-// TODO(TS): replace with Plugin once gr-public-js-api migrated
-interface PluginApi {
- on(eventName: string, adminApi: GrAdminApi): void;
-}
-
/**
* GrAdminApi class.
*
diff --git a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts
index 2db11105..8eeb184 100644
--- a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts
@@ -18,6 +18,7 @@
export class GrAttributeHelper {
private readonly _promises = new Map<string, Promise<any>>();
+ // TOOD(TS): Change any to something more like HTMLElement.
constructor(public element: any) {}
_getChangedEventName(name: string): string {
diff --git a/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.ts b/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.ts
index 8880f06..d3452dc 100644
--- a/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.ts
@@ -1,5 +1,3 @@
-import {GrAttributeHelper} from '../gr-attribute-helper/gr-attribute-helper';
-
/**
* @license
* Copyright (C) 2018 The Android Open Source Project
@@ -16,25 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import {HookApi, PluginApi} from '../gr-plugin-types';
-type HookCallback = (el: Element) => void;
-interface HookApi {
- onAttached(callback: HookCallback): void;
-}
-interface PluginAPI {
- hook(hookname: string): HookApi;
- attributeHelper(element: Element): GrAttributeHelper;
-}
-
-/** @constructor */
export class GrChangeMetadataApi {
- // TODO(TS): Convert to GrDomHook once converted
private _hook: HookApi | null;
- // TODO(TS): Convert type to GrPlugin.
- public plugin: PluginAPI;
+ public plugin: PluginApi;
- constructor(plugin: PluginAPI) {
+ constructor(plugin: PluginApi) {
this.plugin = plugin;
this._hook = null;
}
diff --git a/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.ts b/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.ts
index daf7d67..dd76be4 100644
--- a/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.ts
@@ -15,25 +15,14 @@
* limitations under the License.
*/
import {PolymerElement} from '@polymer/polymer/polymer-element';
+import {HookApi, HookCallback, PluginApi} from '../gr-plugin-types';
-type HookCallback = (el: Element) => void;
-interface HookApi {
- onAttached(callback: HookCallback): void;
-}
-interface PluginAPI {
- hook(hookname: string): HookApi;
- getPluginName(): string;
-}
-
-/** @constructor */
export class GrDomHooksManager {
private _hooks: Record<string, GrDomHook>;
- // TODO(TS): Convert type to GrPlugin.
- private _plugin: PluginAPI;
+ private _plugin: PluginApi;
- // TODO(TS): Convert type to GrPlugin.
- constructor(plugin: PluginAPI) {
+ constructor(plugin: PluginApi) {
this._plugin = plugin;
this._hooks = {};
}
@@ -61,18 +50,8 @@
}
}
-interface PublicApi {
- onAttached(callback: HookCallback): PublicApi;
- onDetached(callback: HookCallback): PublicApi;
- getAllAttached(): any;
- getLastAttached(): any;
- getModuleName(): string;
-}
-
-/** @constructor */
-export class GrDomHook {
- // TODO(TS): specify type for this
- private _instances: unknown[] = [];
+export class GrDomHook implements HookApi {
+ private _instances: HTMLElement[] = [];
private _attachCallbacks: HookCallback[] = [];
@@ -80,7 +59,7 @@
private _moduleName: string;
- private _lastAttachedPromise: Promise<HookCallback> | null = null;
+ private _lastAttachedPromise: Promise<HTMLElement> | null = null;
constructor(hookName: string, moduleName?: string) {
if (moduleName) {
@@ -108,7 +87,7 @@
customElements.define(HookPlaceholder.is, HookPlaceholder);
}
- handleInstanceDetached(instance: Element) {
+ handleInstanceDetached(instance: HTMLElement) {
const index = this._instances.indexOf(instance);
if (index !== -1) {
this._instances.splice(index, 1);
@@ -116,7 +95,7 @@
this._detachCallbacks.forEach(callback => callback(instance));
}
- handleInstanceAttached(instance: Element) {
+ handleInstanceAttached(instance: HTMLElement) {
this._instances.push(instance);
this._attachCallbacks.forEach(callback => callback(instance));
}
@@ -124,27 +103,25 @@
/**
* Get instance of last DOM hook element attached into the endpoint.
* Returns a Promise, that's resolved when attachment is done.
- *
- * @return
*/
- getLastAttached() {
+ getLastAttached(): Promise<HTMLElement> {
if (this._instances.length) {
return Promise.resolve(this._instances.slice(-1)[0]);
}
if (!this._lastAttachedPromise) {
let resolve: HookCallback;
- const promise = new Promise(r => {
+ const promise = new Promise<HTMLElement>(r => {
resolve = r;
this._attachCallbacks.push(resolve);
});
- this._lastAttachedPromise = promise.then(element => {
+ this._lastAttachedPromise = promise.then((element: HTMLElement) => {
this._lastAttachedPromise = null;
const index = this._attachCallbacks.indexOf(resolve);
if (index !== -1) {
this._attachCallbacks.splice(index, 1);
}
return element;
- }) as Promise<HookCallback>;
+ });
}
return this._lastAttachedPromise;
}
@@ -181,14 +158,4 @@
getModuleName() {
return this._moduleName;
}
-
- getPublicAPI(): PublicApi {
- return {
- onAttached: this.onAttached.bind(this),
- onDetached: this.onDetached.bind(this),
- getAllAttached: this.getAllAttached.bind(this),
- getLastAttached: this.getLastAttached.bind(this),
- getModuleName: this.getModuleName.bind(this),
- };
- }
}
diff --git a/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.js b/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.js
index c101396..49223b9 100644
--- a/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.js
+++ b/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.js
@@ -23,18 +23,8 @@
const pluginApi = _testOnly_initGerritPluginApi();
suite('gr-dom-hooks tests', () => {
- const PUBLIC_METHODS =[
- 'onAttached',
- 'onDetached',
- 'getLastAttached',
- 'getAllAttached',
- 'getModuleName',
- ];
-
let instance;
-
let hook;
- let hookInternal;
setup(() => {
let plugin;
@@ -46,16 +36,11 @@
suite('placeholder', () => {
setup(()=>{
sinon.stub(GrDomHook.prototype, '_createPlaceholder');
- hookInternal = instance.getDomHook('foo-bar');
- hook = hookInternal.getPublicAPI();
- });
-
- test('public hook API has only public methods', () => {
- assert.deepEqual(Object.keys(hook).sort(), PUBLIC_METHODS.sort());
+ hook = instance.getDomHook('foo-bar');
});
test('registers placeholder class', () => {
- assert.isTrue(hookInternal._createPlaceholder.calledWithExactly(
+ assert.isTrue(hook._createPlaceholder.calledWithExactly(
'testplugin-autogenerated-foo-bar'));
});
@@ -68,12 +53,7 @@
suite('custom element', () => {
setup(() => {
- hookInternal = instance.getDomHook('foo-bar', 'my-el');
- hook = hookInternal.getPublicAPI();
- });
-
- test('public hook API has only public methods', () => {
- assert.deepEqual(Object.keys(hook).sort(), PUBLIC_METHODS.sort());
+ hook = instance.getDomHook('foo-bar', 'my-el');
});
test('getModuleName()', () => {
@@ -89,8 +69,8 @@
document.createElement(hook.getModuleName()),
document.createElement(hook.getModuleName()),
];
- hookInternal.handleInstanceAttached(el1);
- hookInternal.handleInstanceAttached(el2);
+ hook.handleInstanceAttached(el1);
+ hook.handleInstanceAttached(el2);
assert.isTrue(onAttachedSpy.firstCall.calledWithExactly(el1));
assert.isTrue(onAttachedSpy.secondCall.calledWithExactly(el2));
});
@@ -102,9 +82,9 @@
document.createElement(hook.getModuleName()),
document.createElement(hook.getModuleName()),
];
- hookInternal.handleInstanceDetached(el1);
+ hook.handleInstanceDetached(el1);
assert.isTrue(onDetachedSpy.firstCall.calledWithExactly(el1));
- hookInternal.handleInstanceDetached(el2);
+ hook.handleInstanceDetached(el2);
assert.isTrue(onDetachedSpy.secondCall.calledWithExactly(el2));
});
@@ -115,10 +95,10 @@
];
el1.textContent = 'one';
el2.textContent = 'two';
- hookInternal.handleInstanceAttached(el1);
- hookInternal.handleInstanceAttached(el2);
+ hook.handleInstanceAttached(el1);
+ hook.handleInstanceAttached(el2);
assert.deepEqual([el1, el2], hook.getAllAttached());
- hookInternal.handleInstanceDetached(el1);
+ hook.handleInstanceDetached(el1);
assert.deepEqual([el2], hook.getAllAttached());
});
@@ -131,8 +111,8 @@
];
el1.textContent = 'one';
el2.textContent = 'two';
- hookInternal.handleInstanceAttached(el1);
- hookInternal.handleInstanceAttached(el2);
+ hook.handleInstanceAttached(el1);
+ hook.handleInstanceAttached(el2);
const afterAttachedPromise = hook.getLastAttached().then(
el => assert.strictEqual(el2, el));
return Promise.all([
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-types.ts b/polygerrit-ui/app/elements/plugins/gr-plugin-types.ts
new file mode 100644
index 0000000..7ac670b
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-plugin-types.ts
@@ -0,0 +1,111 @@
+/**
+ * @license
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {GrAttributeHelper} from './gr-attribute-helper/gr-attribute-helper';
+import {GrPluginRestApi} from '../shared/gr-js-api-interface/gr-plugin-rest-api';
+import {GrEventHelper} from './gr-event-helper/gr-event-helper';
+import {GrPopupInterface} from './gr-popup-interface/gr-popup-interface';
+import {GrPluginActionContext} from '../shared/gr-js-api-interface/gr-plugin-action-context';
+import {ConfigInfo} from '../../types/common';
+
+interface GerritElementExtensions {
+ content?: HTMLElement & {hidden?: boolean};
+ change?: unknown;
+ revision?: unknown;
+ token?: string;
+ repoName?: string;
+ config?: ConfigInfo;
+}
+export type HookCallback = (el: HTMLElement & GerritElementExtensions) => void;
+
+export interface HookApi {
+ onAttached(callback: HookCallback): HookApi;
+ onDetached(callback: HookCallback): HookApi;
+ getAllAttached(): HTMLElement[];
+ getLastAttached(): Promise<HTMLElement>;
+ getModuleName(): string;
+}
+
+export enum TargetElement {
+ CHANGE_ACTIONS = 'changeactions',
+ REPLY_DIALOG = 'replydialog',
+}
+
+// Note: for new events, naming convention should be: `a-b`
+export enum EventType {
+ HISTORY = 'history',
+ LABEL_CHANGE = 'labelchange',
+ SHOW_CHANGE = 'showchange',
+ SUBMIT_CHANGE = 'submitchange',
+ SHOW_REVISION_ACTIONS = 'show-revision-actions',
+ COMMIT_MSG_EDIT = 'commitmsgedit',
+ COMMENT = 'comment',
+ REVERT = 'revert',
+ REVERT_SUBMISSION = 'revert_submission',
+ POST_REVERT = 'postrevert',
+ ANNOTATE_DIFF = 'annotatediff',
+ ADMIN_MENU_LINKS = 'admin-menu-links',
+ HIGHLIGHTJS_LOADED = 'highlightjs-loaded',
+}
+
+export interface RegisterOptions {
+ slot?: string;
+ replace: unknown;
+}
+
+export interface PanelInfo {
+ body: Element;
+ p: {[key: string]: any};
+ onUnload: () => void;
+}
+
+export interface SettingsInfo {
+ body: Element;
+ token?: string;
+ onUnload: () => void;
+ setTitle: () => void;
+ setWindowTitle: () => void;
+ show: () => void;
+}
+
+export interface PluginApi {
+ _url?: URL;
+ deprecated: PluginDeprecatedApi;
+ hook(endpointName: string, opt_options?: RegisterOptions): HookApi;
+ getPluginName(): string;
+ on(eventName: string, target: any): void;
+ attributeHelper(element: Element): GrAttributeHelper;
+ restApi(): GrPluginRestApi;
+ eventHelper(element: Node): GrEventHelper;
+}
+
+export interface PluginDeprecatedApi {
+ _loadedGwt(): void;
+ install: () => void;
+ popup(element: Node): GrPopupInterface;
+ onAction(
+ type: string,
+ action: string,
+ callback: (ctx: GrPluginActionContext) => void
+ ): void;
+ panel(extensionpoint: string, callback: (panel: PanelInfo) => void): void;
+ screen(pattern: string, callback: (settings: SettingsInfo) => void): void;
+ settingsScreen(
+ path: string,
+ menu: string,
+ callback: (settings: SettingsInfo) => void
+ ): void;
+}
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.ts b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.ts
index faa5d1e..d45c263 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.ts
@@ -17,17 +17,10 @@
import './gr-plugin-popup';
import {dom, flush} from '@polymer/polymer/lib/legacy/polymer.dom';
import {GrPluginPopup} from './gr-plugin-popup';
-
-// TODO(TS): replace with Plugin API once its migrated to ts
-interface HookApi {
- getLastAttached(): Promise<Node>;
-}
-interface PluginAPI {
- hook(hookname: string): HookApi;
-}
+import {PluginApi} from '../gr-plugin-types';
interface CustomPolymerPluginEl extends HTMLElement {
- plugin: PluginAPI;
+ plugin: PluginApi;
}
/**
@@ -42,14 +35,14 @@
private _popup: GrPluginPopup | null = null;
constructor(
- readonly plugin: PluginAPI,
+ readonly plugin: PluginApi,
private _moduleName: string | null = null
) {}
_getElement() {
// TODO(TS): maybe consider removing this if no one is using
// anything other than native methods on the return
- return dom(this._popup);
+ return (dom(this._popup) as unknown) as HTMLElement;
}
/**
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.js b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.js
index e59d35c..75bd306 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.js
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.js
@@ -21,6 +21,7 @@
import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
+import {createIronOverlayBackdropStyleEl} from '../../../test/test-utils';
class GrUserTestPopupElement extends PolymerElement {
static get is() { return 'gr-user-test-popup'; }
@@ -39,8 +40,10 @@
let container;
let instance;
let plugin;
+ let ironOverlayBackdropStyleEl;
setup(() => {
+ ironOverlayBackdropStyleEl = createIronOverlayBackdropStyleEl();
pluginApi.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
container = containerFixture.instantiate();
@@ -51,6 +54,10 @@
});
});
+ teardown(() => {
+ ironOverlayBackdropStyleEl.remove();
+ });
+
suite('manual', () => {
setup(() => {
instance = new GrPopupInterface(plugin);
diff --git a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.ts b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.ts
index 294205d..701a560 100644
--- a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.ts
@@ -16,21 +16,9 @@
*/
import './gr-plugin-repo-command';
import {ConfigInfo} from '../../../types/common';
+import {HookApi, PluginApi} from '../gr-plugin-types';
-// TODO(TS): replace with Plugin and proper hook once gr-public-js-api migrated
-interface PluginApi {
- hook(endpointName: string, option?: {replace?: boolean}): HookApi;
- eventHelper(
- el: Element
- ): {
- on(name: string, callback: EventListener): void;
- };
-}
-interface HookApi {
- onAttached<T extends Element>(callback: HookCallback<T>): this;
-}
-type HookCallback<T extends Element> = (el: T) => void;
-type RepoCommandCallback = (repo: string, config: ConfigInfo | null) => boolean;
+type RepoCommandCallback = (repo?: string, config?: ConfigInfo) => boolean;
/**
* Parameters provided on repo-command endpoint
@@ -60,7 +48,7 @@
return this._hook;
}
this._hook = this._createHook(title);
- this._hook.onAttached((element: GrRepoCommandEndpointEl) => {
+ this._hook.onAttached(element => {
if (callback(element.repoName, element.config) === false) {
element.hidden = true;
}
@@ -68,7 +56,7 @@
return this;
}
- onTap(callback: EventListener) {
+ onTap(callback: (event: Event) => boolean) {
if (!this._hook) {
console.warn('Call createCommand first.');
return this;
diff --git a/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.ts b/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.ts
index a8bed8d..c7f1ecd 100644
--- a/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.ts
@@ -16,18 +16,7 @@
*/
import '../../settings/gr-settings-view/gr-settings-item';
import '../../settings/gr-settings-view/gr-settings-menu-item';
-
-// TODO(TS): replace with Plugin once gr-public-js-api migrated
-interface PluginApi {
- getPluginName(): string;
- hook(endpointName: string, option?: {replace?: boolean}): HookApi;
-}
-
-interface HookApi {
- onAttached(callback: HookCallback): this;
-}
-
-type HookCallback = (el: Node) => void;
+import {PluginApi} from '../gr-plugin-types';
export class GrSettingsApi {
private _token: string;
diff --git a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.ts b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.ts
index 613bde2..821e4bf 100644
--- a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.ts
@@ -16,16 +16,7 @@
*/
import './gr-custom-plugin-header';
import {GrCustomPluginHeader} from './gr-custom-plugin-header';
-
-// TODO(TS): replace with Plugin once gr-public-js-api migrated
-interface PluginApi {
- hook(
- endpointName: string,
- option: {replace?: boolean}
- ): {
- onAttached(callback: (el: Element) => void): void;
- };
-}
+import {PluginApi} from '../gr-plugin-types';
/**
* Defines api for theme, can be used to set header logo and title.
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 d75781f..80e09d4 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
@@ -19,20 +19,7 @@
import {CoverageRange} from '../../../types/types';
import {Side} from '../../../constants/constants';
import {PatchSetNum} from '../../../types/common';
-
-type HookCallback = (el: {content: Element & {hidden?: boolean}}) => void;
-
-// TODO(TS): remove once Plugin api converted to ts
-interface HookApi {
- onAttached(callback: HookCallback): void;
- onDetached(callback: HookCallback): void;
-}
-
-// TODO(TS): remove once Plugin api converted to ts
-interface PluginApi {
- hook(hookName: string): HookApi;
- on(eventName: string, callback: unknown): void;
-}
+import {PluginApi} from '../../plugins/gr-plugin-types';
type AddLayerFunc = (ctx: GrAnnotationActionsContext) => void;
@@ -49,7 +36,7 @@
side: Side
) => void;
-type CoverageProvider = (
+export type CoverageProvider = (
changeNum: number,
path: string,
basePatchNum: number,
@@ -147,6 +134,10 @@
onAttached: (checkboxEl: Element | null) => void
) {
this.plugin.hook('annotation-toggler').onAttached(element => {
+ if (!element.content) {
+ console.error('plugin endpoint without content.');
+ return;
+ }
if (!element.content.hidden) {
console.error(
element.content.id + ' is already enabled. Cannot re-enable.'
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.ts
index e61a6b5..3a86700 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.ts
@@ -15,12 +15,12 @@
* limitations under the License.
*/
import {
- ApiElement,
GrChangeActions,
ActionType,
ActionPriority,
- JsApiService,
} from '../../../services/services/gr-rest-api/gr-rest-api';
+import {JsApiService} from './gr-js-api-types';
+import {TargetElement} from '../../plugins/gr-plugin-types';
interface Plugin {
getPluginName(): string;
@@ -63,7 +63,11 @@
const sharedApiElement = (document.createElement(
'gr-js-api-interface'
) as unknown) as JsApiService;
- this.setEl(sharedApiElement.getElement(ApiElement.CHANGE_ACTIONS));
+ this.setEl(
+ (sharedApiElement.getElement(
+ TargetElement.CHANGE_ACTIONS
+ ) as unknown) as GrChangeActions
+ );
}
return this._el!;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api.ts
index 7a40c68..7069304 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api.ts
@@ -15,11 +15,9 @@
* limitations under the License.
*/
-import {
- ApiElement,
- GrReplyDialog,
- JsApiService,
-} from '../../../services/services/gr-rest-api/gr-rest-api';
+import {GrReplyDialog} from '../../../services/services/gr-rest-api/gr-rest-api';
+import {PluginApi, TargetElement} from '../../plugins/gr-plugin-types';
+import {JsApiService} from './gr-js-api-types';
// TODO(TS): maybe move interfaces\types to other files when convertion complete
interface LabelsChangedDetail {
@@ -30,56 +28,9 @@
value: string;
}
-interface GerritHtmlElementEventMap {
- 'value-changed': CustomEvent<ValueChangedDetail>;
- 'labels-changed': CustomEvent<LabelsChangedDetail>;
-}
-
-interface GerritHtmlElement extends EventTarget {
- addEventListener<K extends keyof GerritHtmlElementEventMap>(
- type: K,
- listener: (
- this: GerritHtmlElement,
- ev: GerritHtmlElementEventMap[K]
- ) => void,
- options?: boolean | AddEventListenerOptions
- ): void;
- addEventListener(
- type: string,
- listener: EventListenerOrEventListenerObject,
- options?: boolean | AddEventListenerOptions
- ): void;
-
- removeEventListener<K extends keyof GerritHtmlElementEventMap>(
- type: K,
- listener: (
- this: GerritHtmlElement,
- ev: GerritHtmlElementEventMap[K]
- ) => void,
- options?: boolean | EventListenerOptions
- ): void;
- removeEventListener(
- type: string,
- listener: EventListenerOrEventListenerObject,
- options?: boolean | EventListenerOptions
- ): void;
-}
-
-type HookCallback = (el: {content: GerritHtmlElement}) => void;
type ReplyChangedCallback = (text: string) => void;
type LabelsChangedCallback = (detail: LabelsChangedDetail) => void;
-// TODO(TS): remove once Plugin api converted to ts
-interface HookApi {
- onAttached(callback: HookCallback): void;
- onDetached(callback: HookCallback): void;
-}
-
-// TODO(TS): remove once Plugin api converted to ts
-interface PluginApi {
- hook(hookName: string): HookApi;
-}
-
/**
* GrChangeReplyInterface, provides a set of handy methods on reply dialog.
*/
@@ -90,7 +41,9 @@
) {}
get _el(): GrReplyDialog {
- return this.sharedApiElement.getElement(ApiElement.REPLY_DIALOG);
+ return (this.sharedApiElement.getElement(
+ TargetElement.REPLY_DIALOG
+ ) as unknown) as GrReplyDialog;
}
getLabelValue(label: string) {
@@ -107,8 +60,10 @@
addReplyTextChangedCallback(handler: ReplyChangedCallback) {
const hookApi = this.plugin.hook('reply-text');
- const registeredHandler = (e: CustomEvent<ValueChangedDetail>) =>
- handler(e.detail.value);
+ const registeredHandler = (e: Event) => {
+ const ce = e as CustomEvent<ValueChangedDetail>;
+ handler(ce.detail.value);
+ };
hookApi.onAttached(el => {
if (!el.content) {
return;
@@ -125,8 +80,10 @@
addLabelValuesChangedCallback(handler: LabelsChangedCallback) {
const hookApi = this.plugin.hook('reply-label-scores');
- const registeredHandler = (e: CustomEvent<LabelsChangedDetail>) =>
- handler(e.detail);
+ const registeredHandler = (e: Event) => {
+ const ce = e as CustomEvent<LabelsChangedDetail>;
+ handler(ce.detail);
+ };
hookApi.onAttached(el => {
if (!el.content) {
return;
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.js
deleted file mode 100644
index ce65755..0000000
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.js
+++ /dev/null
@@ -1,188 +0,0 @@
-/**
- * @license
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * This defines the Gerrit instance. All methods directly attached to Gerrit
- * should be defined or linked here.
- */
-
-import {pluginLoader} from './gr-plugin-loader.js';
-import {getRestAPI, send} from './gr-api-utils.js';
-import {appContext} from '../../../services/app-context.js';
-
-/**
- * Trigger the preinstalls for bundled plugins.
- * This needs to happen before Gerrit as plugin bundle overrides the Gerrit.
- */
-function flushPreinstalls() {
- if (window.Gerrit.flushPreinstalls) {
- window.Gerrit.flushPreinstalls();
- }
-}
-export const _testOnly_flushPreinstalls = flushPreinstalls;
-
-export function initGerritPluginApi() {
- window.Gerrit = window.Gerrit || {};
- flushPreinstalls();
- initGerritPluginsMethods(window.Gerrit);
- // Preloaded plugins should be installed after Gerrit.install() is set,
- // since plugin preloader substitutes Gerrit.install() temporarily.
- // (Gerrit.install() is set in initGerritPluginsMethods)
- pluginLoader.installPreloadedPlugins();
-}
-
-export function _testOnly_initGerritPluginApi() {
- initGerritPluginApi();
- return window.Gerrit;
-}
-
-export function deprecatedDelete(url, opt_callback) {
- console.warn('.delete() is deprecated! Use plugin.restApi().delete()');
- return getRestAPI().send('DELETE', url)
- .then(response => {
- if (response.status !== 204) {
- return response.text().then(text => {
- if (text) {
- return Promise.reject(new Error(text));
- } else {
- return Promise.reject(new Error(response.status));
- }
- });
- }
- if (opt_callback) {
- opt_callback(response);
- }
- return response;
- });
-}
-
-function initGerritPluginsMethods(globalGerritObj) {
- /**
- * @deprecated Use plugin.styles().css(rulesStr) instead. Please, consult
- * the documentation how to replace it accordingly.
- */
- globalGerritObj.css = function(rulesStr) {
- console.warn('Gerrit.css(rulesStr) is deprecated!',
- 'Use plugin.styles().css(rulesStr)');
- if (!globalGerritObj._customStyleSheet) {
- const styleEl = document.createElement('style');
- document.head.appendChild(styleEl);
- globalGerritObj._customStyleSheet = styleEl.sheet;
- }
-
- const name = '__pg_js_api_class_' +
- globalGerritObj._customStyleSheet.cssRules.length;
- globalGerritObj._customStyleSheet
- .insertRule('.' + name + '{' + rulesStr + '}', 0);
- return name;
- };
-
- globalGerritObj.install = function(callback, opt_version, opt_src) {
- pluginLoader.install(callback, opt_version, opt_src);
- };
-
- globalGerritObj.getLoggedIn = function() {
- console.warn('Gerrit.getLoggedIn() is deprecated! ' +
- 'Use plugin.restApi().getLoggedIn()');
- return document.createElement('gr-rest-api-interface').getLoggedIn();
- };
-
- globalGerritObj.get = function(url, callback) {
- console.warn('.get() is deprecated! Use plugin.restApi().get()');
- send('GET', url, callback);
- };
-
- globalGerritObj.post = function(url, payload, callback) {
- console.warn('.post() is deprecated! Use plugin.restApi().post()');
- send('POST', url, callback, payload);
- };
-
- globalGerritObj.put = function(url, payload, callback) {
- console.warn('.put() is deprecated! Use plugin.restApi().put()');
- send('PUT', url, callback, payload);
- };
-
- globalGerritObj.delete = function(url, opt_callback) {
- deprecatedDelete(url, opt_callback);
- };
-
- globalGerritObj.awaitPluginsLoaded = function() {
- return pluginLoader.awaitPluginsLoaded();
- };
-
- // TODO(taoalpha): consider removing these proxy methods
- // and using pluginLoader directly
- globalGerritObj._loadPlugins = function(plugins, opt_option) {
- pluginLoader.loadPlugins(plugins, opt_option);
- };
-
- globalGerritObj._arePluginsLoaded = function() {
- return pluginLoader.arePluginsLoaded();
- };
-
- globalGerritObj._isPluginPreloaded = function(url) {
- return pluginLoader.isPluginPreloaded(url);
- };
-
- globalGerritObj._isPluginEnabled = function(pathOrUrl) {
- return pluginLoader.isPluginEnabled(pathOrUrl);
- };
-
- globalGerritObj._isPluginLoaded = function(pathOrUrl) {
- return pluginLoader.isPluginLoaded(pathOrUrl);
- };
-
- const eventEmitter = appContext.eventEmitter;
-
- // TODO(taoalpha): List all internal supported event names.
- // Also convert this to inherited class once we move Gerrit to class.
- globalGerritObj._eventEmitter = eventEmitter;
- ['addListener',
- 'dispatch',
- 'emit',
- 'off',
- 'on',
- 'once',
- 'removeAllListeners',
- 'removeListener',
- ].forEach(method => {
- /**
- * Enabling EventEmitter interface on Gerrit.
- *
- * This will enable to signal across different parts of js code without relying on DOM,
- * including core to core, plugin to plugin and also core to plugin.
- *
- * @example
- *
- * // Emit this event from pluginA
- * Gerrit.install(pluginA => {
- * fetch("some-api").then(() => {
- * Gerrit.on("your-special-event", {plugin: pluginA});
- * });
- * });
- *
- * // Listen on your-special-event from pluignB
- * Gerrit.install(pluginB => {
- * Gerrit.on("your-special-event", ({plugin}) => {
- * // do something, plugin is pluginA
- * });
- * });
- */
- globalGerritObj[method] = eventEmitter[method]
- .bind(eventEmitter);
- });
-}
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.ts
new file mode 100644
index 0000000..e5e6069
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.ts
@@ -0,0 +1,252 @@
+/**
+ * @license
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This defines the Gerrit instance. All methods directly attached to Gerrit
+ * should be defined or linked here.
+ */
+import {pluginLoader, PluginOptionMap} from './gr-plugin-loader';
+import {getRestAPI, send} from './gr-api-utils';
+import {appContext} from '../../../services/app-context';
+import {PluginApi} from '../../plugins/gr-plugin-types';
+import {HttpMethod} from '../../../constants/constants';
+import {RequestPayload} from '../../../types/common';
+import {
+ EventCallback,
+ EventEmitterService,
+} from '../../../services/gr-event-interface/gr-event-interface';
+
+interface GerritGlobal extends EventEmitterService {
+ flushPreinstalls?(): void;
+ css(rule: string): string;
+ install(
+ callback: (plugin: PluginApi) => void,
+ opt_version?: string,
+ src?: string
+ ): void;
+ getLoggedIn(): Promise<boolean>;
+ get(url: string, callback?: (response: unknown) => void): void;
+ post(
+ url: string,
+ payload?: RequestPayload,
+ callback?: (response: unknown) => void
+ ): void;
+ put(
+ url: string,
+ payload?: RequestPayload,
+ callback?: (response: unknown) => void
+ ): void;
+ delete(url: string, callback?: (response: unknown) => void): void;
+ isPluginLoaded(pathOrUrl: string): boolean;
+ awaitPluginsLoaded(): Promise<unknown>;
+ _loadPlugins(plugins: string[], opts: PluginOptionMap): void;
+ _arePluginsLoaded(): boolean;
+ _isPluginPreloaded(pathOrUrl: string): boolean;
+ _isPluginEnabled(pathOrUrl: string): boolean;
+ _isPluginLoaded(pathOrUrl: string): boolean;
+ _eventEmitter: EventEmitterService;
+ _customStyleSheet: CSSStyleSheet;
+}
+
+/**
+ * Trigger the preinstalls for bundled plugins.
+ * This needs to happen before Gerrit as plugin bundle overrides the Gerrit.
+ */
+function flushPreinstalls() {
+ const Gerrit = window.Gerrit as GerritGlobal;
+ if (Gerrit.flushPreinstalls) {
+ Gerrit.flushPreinstalls();
+ }
+}
+export const _testOnly_flushPreinstalls = flushPreinstalls;
+
+export function initGerritPluginApi() {
+ window.Gerrit = {};
+ flushPreinstalls();
+ initGerritPluginsMethods(window.Gerrit as GerritGlobal);
+ // Preloaded plugins should be installed after Gerrit.install() is set,
+ // since plugin preloader substitutes Gerrit.install() temporarily.
+ // (Gerrit.install() is set in initGerritPluginsMethods)
+ pluginLoader.installPreloadedPlugins();
+}
+
+export function _testOnly_initGerritPluginApi(): GerritGlobal {
+ initGerritPluginApi();
+ return window.Gerrit as GerritGlobal;
+}
+
+export function deprecatedDelete(
+ url: string,
+ callback?: (response: Response) => void
+) {
+ console.warn('.delete() is deprecated! Use plugin.restApi().delete()');
+ return getRestAPI()
+ .send(HttpMethod.DELETE, url)
+ .then(response => {
+ if (response.status !== 204) {
+ return response.text().then(text => {
+ if (text) {
+ return Promise.reject(new Error(text));
+ } else {
+ return Promise.reject(new Error(`${response.status}`));
+ }
+ });
+ }
+ if (callback) callback(response);
+ return response;
+ });
+}
+
+function initGerritPluginsMethods(globalGerritObj: GerritGlobal) {
+ /**
+ * @deprecated Use plugin.styles().css(rulesStr) instead. Please, consult
+ * the documentation how to replace it accordingly.
+ */
+ globalGerritObj.css = (rulesStr: string) => {
+ console.warn(
+ 'Gerrit.css(rulesStr) is deprecated!',
+ 'Use plugin.styles().css(rulesStr)'
+ );
+ if (!globalGerritObj._customStyleSheet) {
+ const styleEl = document.createElement('style');
+ document.head.appendChild(styleEl);
+ globalGerritObj._customStyleSheet = styleEl.sheet!;
+ }
+
+ const name = `__pg_js_api_class_${globalGerritObj._customStyleSheet.cssRules.length}`;
+ globalGerritObj._customStyleSheet.insertRule(
+ '.' + name + '{' + rulesStr + '}',
+ 0
+ );
+ return name;
+ };
+
+ globalGerritObj.install = (callback, opt_version, opt_src) => {
+ pluginLoader.install(callback, opt_version, opt_src);
+ };
+
+ globalGerritObj.getLoggedIn = () => {
+ console.warn(
+ 'Gerrit.getLoggedIn() is deprecated! ' +
+ 'Use plugin.restApi().getLoggedIn()'
+ );
+ return document.createElement('gr-rest-api-interface').getLoggedIn();
+ };
+
+ globalGerritObj.get = (
+ url: string,
+ callback?: (response: unknown) => void
+ ) => {
+ console.warn('.get() is deprecated! Use plugin.restApi().get()');
+ send(HttpMethod.GET, url, callback);
+ };
+
+ globalGerritObj.post = (
+ url: string,
+ payload?: RequestPayload,
+ callback?: (response: unknown) => void
+ ) => {
+ console.warn('.post() is deprecated! Use plugin.restApi().post()');
+ send(HttpMethod.POST, url, callback, payload);
+ };
+
+ globalGerritObj.put = (
+ url: string,
+ payload?: RequestPayload,
+ callback?: (response: unknown) => void
+ ) => {
+ console.warn('.put() is deprecated! Use plugin.restApi().put()');
+ send(HttpMethod.PUT, url, callback, payload);
+ };
+
+ globalGerritObj.delete = (
+ url: string,
+ callback?: (response: Response) => void
+ ) => {
+ deprecatedDelete(url, callback);
+ };
+
+ globalGerritObj.awaitPluginsLoaded = () => {
+ return pluginLoader.awaitPluginsLoaded();
+ };
+
+ // TODO(taoalpha): consider removing these proxy methods
+ // and using pluginLoader directly
+ globalGerritObj._loadPlugins = (plugins, opt_option) => {
+ pluginLoader.loadPlugins(plugins, opt_option);
+ };
+
+ globalGerritObj._arePluginsLoaded = () => {
+ return pluginLoader.arePluginsLoaded();
+ };
+
+ globalGerritObj._isPluginPreloaded = url => {
+ return pluginLoader.isPluginPreloaded(url);
+ };
+
+ globalGerritObj._isPluginEnabled = pathOrUrl => {
+ return pluginLoader.isPluginEnabled(pathOrUrl);
+ };
+
+ globalGerritObj._isPluginLoaded = pathOrUrl => {
+ return pluginLoader.isPluginLoaded(pathOrUrl);
+ };
+
+ const eventEmitter = appContext.eventEmitter;
+
+ // TODO(taoalpha): List all internal supported event names.
+ // Also convert this to inherited class once we move Gerrit to class.
+ globalGerritObj._eventEmitter = eventEmitter;
+ /**
+ * Enabling EventEmitter interface on Gerrit.
+ *
+ * This will enable to signal across different parts of js code without relying on DOM,
+ * including core to core, plugin to plugin and also core to plugin.
+ *
+ * @example
+ *
+ * // Emit this event from pluginA
+ * Gerrit.install(pluginA => {
+ * fetch("some-api").then(() => {
+ * Gerrit.on("your-special-event", {plugin: pluginA});
+ * });
+ * });
+ *
+ * // Listen on your-special-event from pluignB
+ * Gerrit.install(pluginB => {
+ * Gerrit.on("your-special-event", ({plugin}) => {
+ * // do something, plugin is pluginA
+ * });
+ * });
+ */
+ globalGerritObj.addListener = (eventName: string, cb: EventCallback) =>
+ eventEmitter.addListener(eventName, cb);
+ globalGerritObj.dispatch = (eventName: string, detail: any) =>
+ eventEmitter.dispatch(eventName, detail);
+ globalGerritObj.emit = (eventName: string, detail: any) =>
+ eventEmitter.emit(eventName, detail);
+ globalGerritObj.off = (eventName: string, cb: EventCallback) =>
+ eventEmitter.off(eventName, cb);
+ globalGerritObj.on = (eventName: string, cb: EventCallback) =>
+ eventEmitter.on(eventName, cb);
+ globalGerritObj.once = (eventName: string, cb: EventCallback) =>
+ eventEmitter.once(eventName, cb);
+ globalGerritObj.removeAllListeners = (eventName: string) =>
+ eventEmitter.removeAllListeners(eventName);
+ globalGerritObj.removeListener = (eventName: string, cb: EventCallback) =>
+ eventEmitter.removeListener(eventName, cb);
+}
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.js
deleted file mode 100644
index 6c785d4..0000000
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.js
+++ /dev/null
@@ -1,327 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
-import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
-import {PolymerElement} from '@polymer/polymer/polymer-element.js';
-import {pluginLoader} from './gr-plugin-loader.js';
-import {patchNumEquals} from '../../../utils/patch-set-util.js';
-
-// Note: for new events, naming convention should be: `a-b`
-const EventType = {
- HISTORY: 'history',
- LABEL_CHANGE: 'labelchange',
- SHOW_CHANGE: 'showchange',
- SUBMIT_CHANGE: 'submitchange',
- SHOW_REVISION_ACTIONS: 'show-revision-actions',
- COMMIT_MSG_EDIT: 'commitmsgedit',
- COMMENT: 'comment',
- REVERT: 'revert',
- REVERT_SUBMISSION: 'revert_submission',
- POST_REVERT: 'postrevert',
- ANNOTATE_DIFF: 'annotatediff',
- ADMIN_MENU_LINKS: 'admin-menu-links',
- HIGHLIGHTJS_LOADED: 'highlightjs-loaded',
-};
-
-const Element = {
- CHANGE_ACTIONS: 'changeactions',
- REPLY_DIALOG: 'replydialog',
-};
-
-/**
- * @extends PolymerElement
- */
-class GrJsApiInterface extends GestureEventListeners(
- LegacyElementMixin(
- PolymerElement)) {
- static get is() { return 'gr-js-api-interface'; }
-
- constructor() {
- super();
- this.Element = Element;
- this.EventType = EventType;
- }
-
- static get properties() {
- return {
- _elements: {
- type: Object,
- value: {}, // Shared across all instances.
- },
- _eventCallbacks: {
- type: Object,
- value: {}, // Shared across all instances.
- },
- };
- }
-
- handleEvent(type, detail) {
- pluginLoader.awaitPluginsLoaded().then(() => {
- switch (type) {
- case EventType.HISTORY:
- this._handleHistory(detail);
- break;
- case EventType.SHOW_CHANGE:
- this._handleShowChange(detail);
- break;
- case EventType.COMMENT:
- this._handleComment(detail);
- break;
- case EventType.LABEL_CHANGE:
- this._handleLabelChange(detail);
- break;
- case EventType.SHOW_REVISION_ACTIONS:
- this._handleShowRevisionActions(detail);
- break;
- case EventType.HIGHLIGHTJS_LOADED:
- this._handleHighlightjsLoaded(detail);
- break;
- default:
- console.warn('handleEvent called with unsupported event type:',
- type);
- break;
- }
- });
- }
-
- addElement(key, el) {
- this._elements[key] = el;
- }
-
- getElement(key) {
- return this._elements[key];
- }
-
- addEventCallback(eventName, callback) {
- if (!this._eventCallbacks[eventName]) {
- this._eventCallbacks[eventName] = [];
- }
- this._eventCallbacks[eventName].push(callback);
- }
-
- canSubmitChange(change, revision) {
- const submitCallbacks = this._getEventCallbacks(EventType.SUBMIT_CHANGE);
- const cancelSubmit = submitCallbacks.some(callback => {
- try {
- return callback(change, revision) === false;
- } catch (err) {
- console.error(err);
- }
- return false;
- });
-
- return !cancelSubmit;
- }
-
- _removeEventCallbacks() {
- for (const k in EventType) {
- if (!EventType.hasOwnProperty(k)) { continue; }
- this._eventCallbacks[EventType[k]] = [];
- }
- }
-
- _handleHistory(detail) {
- for (const cb of this._getEventCallbacks(EventType.HISTORY)) {
- try {
- cb(detail.path);
- } catch (err) {
- console.error(err);
- }
- }
- }
-
- _handleShowChange(detail) {
- // Note (issue 8221) Shallow clone the change object and add a mergeable
- // getter with deprecation warning. This makes the change detail appear as
- // though SKIP_MERGEABLE was not set, so that plugins that expect it can
- // still access.
- //
- // This clone and getter can be removed after plugins migrate to use
- // info.mergeable.
- //
- // assign on getter with existing property will report error
- // see Issue: 12286
- const change = {...detail.change, get mergeable() {
- console.warn('Accessing change.mergeable from SHOW_CHANGE is ' +
- 'deprecated! Use info.mergeable instead.');
- return detail.info && detail.info.mergeable;
- }};
- const patchNum = detail.patchNum;
- const info = detail.info;
-
- let revision;
- for (const rev of Object.values(change.revisions || {})) {
- if (patchNumEquals(rev._number, patchNum)) {
- revision = rev;
- break;
- }
- }
-
- for (const cb of this._getEventCallbacks(EventType.SHOW_CHANGE)) {
- try {
- cb(change, revision, info);
- } catch (err) {
- console.error(err);
- }
- }
- }
-
- /**
- * @param {!{change: !Object, revisionActions: !Object}} detail
- */
- _handleShowRevisionActions(detail) {
- const registeredCallbacks = this._getEventCallbacks(
- EventType.SHOW_REVISION_ACTIONS
- );
- for (const cb of registeredCallbacks) {
- try {
- cb(detail.revisionActions, detail.change);
- } catch (err) {
- console.error(err);
- }
- }
- }
-
- handleCommitMessage(change, msg) {
- for (const cb of this._getEventCallbacks(EventType.COMMIT_MSG_EDIT)) {
- try {
- cb(change, msg);
- } catch (err) {
- console.error(err);
- }
- }
- }
-
- _handleComment(detail) {
- for (const cb of this._getEventCallbacks(EventType.COMMENT)) {
- try {
- cb(detail.node);
- } catch (err) {
- console.error(err);
- }
- }
- }
-
- _handleLabelChange(detail) {
- for (const cb of this._getEventCallbacks(EventType.LABEL_CHANGE)) {
- try {
- cb(detail.change);
- } catch (err) {
- console.error(err);
- }
- }
- }
-
- _handleHighlightjsLoaded(detail) {
- for (const cb of this._getEventCallbacks(EventType.HIGHLIGHTJS_LOADED)) {
- try {
- cb(detail.hljs);
- } catch (err) {
- console.error(err);
- }
- }
- }
-
- modifyRevertMsg(change, revertMsg, origMsg) {
- for (const cb of this._getEventCallbacks(EventType.REVERT)) {
- try {
- revertMsg = cb(change, revertMsg, origMsg);
- } catch (err) {
- console.error(err);
- }
- }
- return revertMsg;
- }
-
- modifyRevertSubmissionMsg(change, revertSubmissionMsg, origMsg) {
- for (const cb of this._getEventCallbacks(EventType.REVERT_SUBMISSION)) {
- try {
- revertSubmissionMsg = cb(change, revertSubmissionMsg, origMsg);
- } catch (err) {
- console.error(err);
- }
- }
- return revertSubmissionMsg;
- }
-
- getDiffLayers(path, changeNum, patchNum) {
- const layers = [];
- for (const annotationApi of
- this._getEventCallbacks(EventType.ANNOTATE_DIFF)) {
- try {
- const layer = annotationApi.getLayer(path, changeNum, patchNum);
- layers.push(layer);
- } catch (err) {
- console.error(err);
- }
- }
- return layers;
- }
-
- disposeDiffLayers(path) {
- for (const annotationApi of
- this._getEventCallbacks(EventType.ANNOTATE_DIFF)) {
- try {
- annotationApi.disposeLayer(path);
- } catch (err) {
- console.error(err);
- }
- }
- }
-
- /**
- * Retrieves coverage data possibly provided by a plugin.
- *
- * Will wait for plugins to be loaded. If multiple plugins offer a coverage
- * provider, the first one is returned. If no plugin offers a coverage provider,
- * will resolve to null.
- *
- * @return {!Promise<?GrAnnotationActionsInterface>}
- */
- getCoverageAnnotationApi() {
- return pluginLoader.awaitPluginsLoaded()
- .then(() => this._getEventCallbacks(EventType.ANNOTATE_DIFF)
- .find(api => api.getCoverageProvider()));
- }
-
- getAdminMenuLinks() {
- const links = [];
- for (const adminApi of
- this._getEventCallbacks(EventType.ADMIN_MENU_LINKS)) {
- links.push(...adminApi.getMenuLinks());
- }
- return links;
- }
-
- getLabelValuesPostRevert(change) {
- let labels = {};
- for (const cb of this._getEventCallbacks(EventType.POST_REVERT)) {
- try {
- labels = cb(change);
- } catch (err) {
- console.error(err);
- }
- }
- return labels;
- }
-
- _getEventCallbacks(type) {
- return this._eventCallbacks[type] || [];
- }
-}
-
-customElements.define(GrJsApiInterface.is, GrJsApiInterface);
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
new file mode 100644
index 0000000..4973594
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.ts
@@ -0,0 +1,316 @@
+/**
+ * @license
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
+import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
+import {PolymerElement} from '@polymer/polymer/polymer-element';
+import {pluginLoader} from './gr-plugin-loader';
+import {patchNumEquals} from '../../../utils/patch-set-util';
+import {customElement} from '@polymer/decorators';
+import {ChangeInfo, RevisionInfo} from '../../../types/common';
+import {
+ CoverageProvider,
+ GrAnnotationActionsInterface,
+} from './gr-annotation-actions-js-api';
+import {GrAdminApi} from '../../plugins/gr-admin-api/gr-admin-api';
+import {
+ JsApiService,
+ EventCallback,
+ ShowChangeDetail,
+ ShowRevisionActionsDetail,
+} from './gr-js-api-types';
+import {EventType, TargetElement} from '../../plugins/gr-plugin-types';
+
+const elements: {[key: string]: HTMLElement} = {};
+const eventCallbacks: {[key: string]: EventCallback[]} = {};
+
+@customElement('gr-js-api-interface')
+export class GrJsApiInterface
+ extends GestureEventListeners(LegacyElementMixin(PolymerElement))
+ implements JsApiService {
+ handleEvent(type: EventType, detail: any) {
+ pluginLoader.awaitPluginsLoaded().then(() => {
+ switch (type) {
+ case EventType.HISTORY:
+ this._handleHistory(detail);
+ break;
+ case EventType.SHOW_CHANGE:
+ this._handleShowChange(detail);
+ break;
+ case EventType.COMMENT:
+ this._handleComment(detail);
+ break;
+ case EventType.LABEL_CHANGE:
+ this._handleLabelChange(detail);
+ break;
+ case EventType.SHOW_REVISION_ACTIONS:
+ this._handleShowRevisionActions(detail);
+ break;
+ case EventType.HIGHLIGHTJS_LOADED:
+ this._handleHighlightjsLoaded(detail);
+ break;
+ default:
+ console.warn('handleEvent called with unsupported event type:', type);
+ break;
+ }
+ });
+ }
+
+ addElement(key: TargetElement, el: HTMLElement) {
+ elements[key] = el;
+ }
+
+ getElement(key: TargetElement) {
+ return elements[key];
+ }
+
+ addEventCallback(eventName: EventType, callback: EventCallback) {
+ if (!eventCallbacks[eventName]) {
+ eventCallbacks[eventName] = [];
+ }
+ eventCallbacks[eventName].push(callback);
+ }
+
+ canSubmitChange(change: ChangeInfo, revision: RevisionInfo) {
+ const submitCallbacks = this._getEventCallbacks(EventType.SUBMIT_CHANGE);
+ const cancelSubmit = submitCallbacks.some(callback => {
+ try {
+ return callback(change, revision) === false;
+ } catch (err) {
+ console.error(err);
+ }
+ return false;
+ });
+
+ return !cancelSubmit;
+ }
+
+ /** For testing only. */
+ _removeEventCallbacks() {
+ for (const type of Object.values(EventType)) {
+ eventCallbacks[type] = [];
+ }
+ }
+
+ // TODO(TS): The HISTORY event and its handler seem unused.
+ _handleHistory(detail: {path: string}) {
+ for (const cb of this._getEventCallbacks(EventType.HISTORY)) {
+ try {
+ cb(detail.path);
+ } catch (err) {
+ console.error(err);
+ }
+ }
+ }
+
+ _handleShowChange(detail: ShowChangeDetail) {
+ // Note (issue 8221) Shallow clone the change object and add a mergeable
+ // getter with deprecation warning. This makes the change detail appear as
+ // though SKIP_MERGEABLE was not set, so that plugins that expect it can
+ // still access.
+ //
+ // This clone and getter can be removed after plugins migrate to use
+ // info.mergeable.
+ //
+ // assign on getter with existing property will report error
+ // see Issue: 12286
+ const change = {
+ ...detail.change,
+ get mergeable() {
+ console.warn(
+ 'Accessing change.mergeable from SHOW_CHANGE is ' +
+ 'deprecated! Use info.mergeable instead.'
+ );
+ return detail.info && detail.info.mergeable;
+ },
+ };
+ const patchNum = detail.patchNum;
+ const info = detail.info;
+
+ let revision;
+ for (const rev of Object.values(change.revisions || {})) {
+ if (patchNumEquals(rev._number, patchNum)) {
+ revision = rev;
+ break;
+ }
+ }
+
+ for (const cb of this._getEventCallbacks(EventType.SHOW_CHANGE)) {
+ try {
+ cb(change, revision, info);
+ } catch (err) {
+ console.error(err);
+ }
+ }
+ }
+
+ _handleShowRevisionActions(detail: ShowRevisionActionsDetail) {
+ const registeredCallbacks = this._getEventCallbacks(
+ EventType.SHOW_REVISION_ACTIONS
+ );
+ for (const cb of registeredCallbacks) {
+ try {
+ cb(detail.revisionActions, detail.change);
+ } catch (err) {
+ console.error(err);
+ }
+ }
+ }
+
+ handleCommitMessage(change: ChangeInfo, msg: string) {
+ for (const cb of this._getEventCallbacks(EventType.COMMIT_MSG_EDIT)) {
+ try {
+ cb(change, msg);
+ } catch (err) {
+ console.error(err);
+ }
+ }
+ }
+
+ // TODO(TS): The COMMENT event and its handler seem unused.
+ _handleComment(detail: {node: Node}) {
+ for (const cb of this._getEventCallbacks(EventType.COMMENT)) {
+ try {
+ cb(detail.node);
+ } catch (err) {
+ console.error(err);
+ }
+ }
+ }
+
+ _handleLabelChange(detail: {change: ChangeInfo}) {
+ for (const cb of this._getEventCallbacks(EventType.LABEL_CHANGE)) {
+ try {
+ cb(detail.change);
+ } catch (err) {
+ console.error(err);
+ }
+ }
+ }
+
+ _handleHighlightjsLoaded(detail: {hljs: unknown}) {
+ for (const cb of this._getEventCallbacks(EventType.HIGHLIGHTJS_LOADED)) {
+ try {
+ cb(detail.hljs);
+ } catch (err) {
+ console.error(err);
+ }
+ }
+ }
+
+ modifyRevertMsg(change: ChangeInfo, revertMsg: string, origMsg: string) {
+ for (const cb of this._getEventCallbacks(EventType.REVERT)) {
+ try {
+ revertMsg = cb(change, revertMsg, origMsg) as string;
+ } catch (err) {
+ console.error(err);
+ }
+ }
+ return revertMsg;
+ }
+
+ modifyRevertSubmissionMsg(
+ change: ChangeInfo,
+ revertSubmissionMsg: string,
+ origMsg: string
+ ) {
+ for (const cb of this._getEventCallbacks(EventType.REVERT_SUBMISSION)) {
+ try {
+ revertSubmissionMsg = cb(
+ change,
+ revertSubmissionMsg,
+ origMsg
+ ) as string;
+ } catch (err) {
+ console.error(err);
+ }
+ }
+ return revertSubmissionMsg;
+ }
+
+ getDiffLayers(path: string, changeNum: number, patchNum: number) {
+ const layers = [];
+ for (const cb of this._getEventCallbacks(EventType.ANNOTATE_DIFF)) {
+ const annotationApi = (cb as unknown) as GrAnnotationActionsInterface;
+ try {
+ const layer = annotationApi.getLayer(path, changeNum, patchNum);
+ layers.push(layer);
+ } catch (err) {
+ console.error(err);
+ }
+ }
+ return layers;
+ }
+
+ disposeDiffLayers(path: string) {
+ for (const cb of this._getEventCallbacks(EventType.ANNOTATE_DIFF)) {
+ try {
+ const annotationApi = (cb as unknown) as GrAnnotationActionsInterface;
+ annotationApi.disposeLayer(path);
+ } catch (err) {
+ console.error(err);
+ }
+ }
+ }
+
+ /**
+ * Retrieves coverage data possibly provided by a plugin.
+ *
+ * Will wait for plugins to be loaded. If multiple plugins offer a coverage
+ * provider, the first one is returned. If no plugin offers a coverage provider,
+ * will resolve to null.
+ */
+ getCoverageAnnotationApi(): Promise<CoverageProvider | undefined> {
+ return pluginLoader.awaitPluginsLoaded().then(
+ () =>
+ this._getEventCallbacks(EventType.ANNOTATE_DIFF).find(cb => {
+ const annotationApi = (cb as unknown) as GrAnnotationActionsInterface;
+ return annotationApi.getCoverageProvider();
+ }) as CoverageProvider | undefined
+ );
+ }
+
+ getAdminMenuLinks() {
+ const links = [];
+ for (const cb of this._getEventCallbacks(EventType.ADMIN_MENU_LINKS)) {
+ const adminApi = (cb as unknown) as GrAdminApi;
+ links.push(...adminApi.getMenuLinks());
+ }
+ return links;
+ }
+
+ getLabelValuesPostRevert(change: ChangeInfo) {
+ let labels = {};
+ for (const cb of this._getEventCallbacks(EventType.POST_REVERT)) {
+ try {
+ labels = cb(change);
+ } catch (err) {
+ console.error(err);
+ }
+ }
+ return labels;
+ }
+
+ _getEventCallbacks(type: EventType) {
+ return eventCallbacks[type] || [];
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'gr-js-api-interface': JsApiService & Element;
+ }
+}
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js
deleted file mode 100644
index 6f0ade9..0000000
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * @license
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import '../gr-rest-api-interface/gr-rest-api-interface.js';
-import './gr-js-api-interface-element.js';
-import './gr-public-js-api.js';
-import './gr-gerrit.js';
-
-/*
- Note: the order matters as files depend on each other.
- 1. gr-api-utils will be used in multiple files below.
- 2. gr-gerrit depends on gr-plugin-loader, gr-public-js-api and
- also gr-plugin-endpoints
- 3. gr-public-js-api depends on gr-plugin-rest-api
-*/
-/*
- FIXME(polymer-modulizer): the above comments were extracted
- from HTML and may be out of place here. Review them and
- then delete this comment!
-*/
-
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.ts
new file mode 100644
index 0000000..b9a6ff4
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.ts
@@ -0,0 +1,20 @@
+/**
+ * @license
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import '../gr-rest-api-interface/gr-rest-api-interface';
+import './gr-js-api-interface-element';
+import './gr-public-js-api';
+import './gr-gerrit';
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 2a11f62..272a13b 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
@@ -19,6 +19,7 @@
import './gr-js-api-interface.js';
import {GrPopupInterface} from '../../plugins/gr-popup-interface/gr-popup-interface.js';
import {GrSettingsApi} from '../../plugins/gr-settings-api/gr-settings-api.js';
+import {EventType} from '../../plugins/gr-plugin-types.js';
import {GrPluginActionContext} from './gr-plugin-action-context.js';
import {PLUGIN_LOADING_TIMEOUT_MS} from './gr-api-utils.js';
import {pluginLoader} from './gr-plugin-loader.js';
@@ -183,13 +184,13 @@
});
test('history event', done => {
- plugin.on(element.EventType.HISTORY, throwErrFn);
- plugin.on(element.EventType.HISTORY, path => {
+ plugin.on(EventType.HISTORY, throwErrFn);
+ plugin.on(EventType.HISTORY, path => {
assert.equal(path, '/path/to/awesomesauce');
assert.isTrue(errorStub.calledOnce);
done();
});
- element.handleEvent(element.EventType.HISTORY,
+ element.handleEvent(EventType.HISTORY,
{path: '/path/to/awesomesauce'});
});
@@ -199,15 +200,15 @@
revisions: {def: {_number: 2}, abc: {_number: 1}},
};
const expectedChange = {mergeable: false, ...testChange};
- plugin.on(element.EventType.SHOW_CHANGE, throwErrFn);
- plugin.on(element.EventType.SHOW_CHANGE, (change, revision, info) => {
+ plugin.on(EventType.SHOW_CHANGE, throwErrFn);
+ plugin.on(EventType.SHOW_CHANGE, (change, revision, info) => {
assert.deepEqual(change, expectedChange);
assert.deepEqual(revision, testChange.revisions.abc);
assert.deepEqual(info, {mergeable: false});
assert.isTrue(errorStub.calledOnce);
done();
});
- element.handleEvent(element.EventType.SHOW_CHANGE,
+ element.handleEvent(EventType.SHOW_CHANGE,
{change: testChange, patchNum: 1, info: {mergeable: false}});
});
@@ -216,14 +217,14 @@
_number: 42,
revisions: {def: {_number: 2}, abc: {_number: 1}},
};
- plugin.on(element.EventType.SHOW_REVISION_ACTIONS, throwErrFn);
- plugin.on(element.EventType.SHOW_REVISION_ACTIONS, (actions, change) => {
+ plugin.on(EventType.SHOW_REVISION_ACTIONS, throwErrFn);
+ plugin.on(EventType.SHOW_REVISION_ACTIONS, (actions, change) => {
assert.deepEqual(change, testChange);
assert.deepEqual(actions, {test: {}});
assert.isTrue(errorStub.calledOnce);
done();
});
- element.handleEvent(element.EventType.SHOW_REVISION_ACTIONS,
+ element.handleEvent(EventType.SHOW_REVISION_ACTIONS,
{change: testChange, revisionActions: {test: {}}});
});
@@ -234,8 +235,8 @@
};
const spy = sinon.spy();
pluginLoader.loadPlugins(['plugins/test.html']);
- plugin.on(element.EventType.SHOW_CHANGE, spy);
- element.handleEvent(element.EventType.SHOW_CHANGE,
+ plugin.on(EventType.SHOW_CHANGE, spy);
+ element.handleEvent(EventType.SHOW_CHANGE,
{change: testChange, patchNum: 1});
assert.isFalse(spy.called);
@@ -250,13 +251,13 @@
test('comment event', done => {
const testCommentNode = {foo: 'bar'};
- plugin.on(element.EventType.COMMENT, throwErrFn);
- plugin.on(element.EventType.COMMENT, commentNode => {
+ plugin.on(EventType.COMMENT, throwErrFn);
+ plugin.on(EventType.COMMENT, commentNode => {
assert.deepEqual(commentNode, testCommentNode);
assert.isTrue(errorStub.calledOnce);
done();
});
- element.handleEvent(element.EventType.COMMENT, {node: testCommentNode});
+ element.handleEvent(EventType.COMMENT, {node: testCommentNode});
});
test('revert event', () => {
@@ -267,13 +268,13 @@
assert.equal(element.modifyRevertMsg(null, 'test', 'origTest'), 'test');
assert.equal(errorStub.callCount, 0);
- plugin.on(element.EventType.REVERT, throwErrFn);
- plugin.on(element.EventType.REVERT, appendToRevertMsg);
+ plugin.on(EventType.REVERT, throwErrFn);
+ plugin.on(EventType.REVERT, appendToRevertMsg);
assert.equal(element.modifyRevertMsg(null, 'test', 'origTest'),
'test\n> origTest\ninfo');
assert.isTrue(errorStub.calledOnce);
- plugin.on(element.EventType.REVERT, appendToRevertMsg);
+ plugin.on(EventType.REVERT, appendToRevertMsg);
assert.equal(element.modifyRevertMsg(null, 'test', 'origTest'),
'test\n> origTest\ninfo\n> origTest\ninfo');
assert.isTrue(errorStub.calledTwice);
@@ -287,8 +288,8 @@
assert.deepEqual(element.getLabelValuesPostRevert(null), {});
assert.equal(errorStub.callCount, 0);
- plugin.on(element.EventType.POST_REVERT, throwErrFn);
- plugin.on(element.EventType.POST_REVERT, getLabels);
+ plugin.on(EventType.POST_REVERT, throwErrFn);
+ plugin.on(EventType.POST_REVERT, getLabels);
assert.deepEqual(
element.getLabelValuesPostRevert(null), {'Code-Review': 1});
assert.isTrue(errorStub.calledOnce);
@@ -296,8 +297,8 @@
test('commitmsgedit event', done => {
const testMsg = 'Test CL commit message';
- plugin.on(element.EventType.COMMIT_MSG_EDIT, throwErrFn);
- plugin.on(element.EventType.COMMIT_MSG_EDIT, (change, msg) => {
+ plugin.on(EventType.COMMIT_MSG_EDIT, throwErrFn);
+ plugin.on(EventType.COMMIT_MSG_EDIT, (change, msg) => {
assert.deepEqual(msg, testMsg);
assert.isTrue(errorStub.calledOnce);
done();
@@ -307,35 +308,35 @@
test('labelchange event', done => {
const testChange = {_number: 42};
- plugin.on(element.EventType.LABEL_CHANGE, throwErrFn);
- plugin.on(element.EventType.LABEL_CHANGE, change => {
+ plugin.on(EventType.LABEL_CHANGE, throwErrFn);
+ plugin.on(EventType.LABEL_CHANGE, change => {
assert.deepEqual(change, testChange);
assert.isTrue(errorStub.calledOnce);
done();
});
- element.handleEvent(element.EventType.LABEL_CHANGE, {change: testChange});
+ element.handleEvent(EventType.LABEL_CHANGE, {change: testChange});
});
test('submitchange', () => {
- plugin.on(element.EventType.SUBMIT_CHANGE, throwErrFn);
- plugin.on(element.EventType.SUBMIT_CHANGE, () => true);
+ plugin.on(EventType.SUBMIT_CHANGE, throwErrFn);
+ plugin.on(EventType.SUBMIT_CHANGE, () => true);
assert.isTrue(element.canSubmitChange());
assert.isTrue(errorStub.calledOnce);
- plugin.on(element.EventType.SUBMIT_CHANGE, () => false);
- plugin.on(element.EventType.SUBMIT_CHANGE, () => true);
+ plugin.on(EventType.SUBMIT_CHANGE, () => false);
+ plugin.on(EventType.SUBMIT_CHANGE, () => true);
assert.isFalse(element.canSubmitChange());
assert.isTrue(errorStub.calledTwice);
});
test('highlightjs-loaded event', done => {
const testHljs = {_number: 42};
- plugin.on(element.EventType.HIGHLIGHTJS_LOADED, throwErrFn);
- plugin.on(element.EventType.HIGHLIGHTJS_LOADED, hljs => {
+ plugin.on(EventType.HIGHLIGHTJS_LOADED, throwErrFn);
+ plugin.on(EventType.HIGHLIGHTJS_LOADED, hljs => {
assert.deepEqual(hljs, testHljs);
assert.isTrue(errorStub.calledOnce);
done();
});
- element.handleEvent(element.EventType.HIGHLIGHTJS_LOADED, {hljs: testHljs});
+ element.handleEvent(EventType.HIGHLIGHTJS_LOADED, {hljs: testHljs});
});
test('getLoggedIn', done => {
@@ -353,6 +354,8 @@
});
test('deprecated.install', () => {
+ assert.notStrictEqual(plugin.popup, plugin.deprecated.popup);
+ assert.notStrictEqual(plugin.onAction, plugin.deprecated.onAction);
plugin.deprecated.install();
assert.strictEqual(plugin.popup, plugin.deprecated.popup);
assert.strictEqual(plugin.onAction, plugin.deprecated.onAction);
@@ -370,7 +373,7 @@
assert.deepEqual(result, links);
assert.isTrue(getCallbacksStub.calledOnce);
assert.equal(getCallbacksStub.lastCall.args[0],
- element.EventType.ADMIN_MENU_LINKS);
+ EventType.ADMIN_MENU_LINKS);
});
suite('test plugin with base url', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-types.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-types.ts
new file mode 100644
index 0000000..4b597a5
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-types.ts
@@ -0,0 +1,37 @@
+/**
+ * @license
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {ActionInfo, ChangeInfo, PatchSetNum} from '../../../types/common';
+import {EventType, TargetElement} from '../../plugins/gr-plugin-types';
+
+export interface ShowChangeDetail {
+ change: ChangeInfo;
+ patchNum: PatchSetNum;
+ info: {mergeable: boolean};
+}
+
+export interface ShowRevisionActionsDetail {
+ change: ChangeInfo;
+ revisionActions: {[key: string]: ActionInfo};
+}
+
+export type EventCallback = (...args: any[]) => any;
+
+export interface JsApiService {
+ getElement(key: TargetElement): HTMLElement;
+ addEventCallback(eventName: EventType, callback: EventCallback): void;
+ // TODO(TS): Add more methods when needed for the TS conversion.
+}
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context.ts
index 38e11d6..34ecd9e 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context.ts
@@ -15,19 +15,13 @@
* limitations under the License.
*/
-import {GrPluginRestApi} from './gr-plugin-rest-api';
-import {GrEventHelper} from '../../plugins/gr-event-helper/gr-event-helper';
-import {RevisionInfo, ChangeInfo, RequestPayload} from '../../../types/common';
-import {HttpMethod} from '../../../constants/constants';
-
-interface PluginApi {
- restApi(): GrPluginRestApi;
- deprecated: PluginDeprecatedApi;
- eventHelper(element: Node): GrEventHelper;
-}
-interface PluginDeprecatedApi {
- popup(element: Node): GrPopupInterface;
-}
+import {
+ RevisionInfo,
+ ChangeInfo,
+ RequestPayload,
+ ActionInfo,
+} from '../../../types/common';
+import {PluginApi} from '../../plugins/gr-plugin-types';
interface GrPopupInterface {
close(): void;
@@ -37,18 +31,12 @@
onclick: (event: Event) => boolean;
}
-interface ActionInterface {
- method: HttpMethod;
- __url: string;
- __key: string;
-}
-
export class GrPluginActionContext {
private _popups: GrPopupInterface[] = [];
constructor(
public readonly plugin: PluginApi,
- public readonly action: ActionInterface,
+ public readonly action: ActionInfo,
public readonly change: ChangeInfo,
public readonly revision: RevisionInfo
) {}
@@ -115,6 +103,7 @@
}
call(payload: RequestPayload, onSuccess: (result: unknown) => void) {
+ if (!this.action.method) return;
if (!this.action.__url) {
console.warn(`Unable to ${this.action.method} to ${this.action.__key}!`);
return;
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts
index db6d14a..2aabf34 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts
@@ -16,17 +16,17 @@
*/
import {importHref} from '../../../scripts/import-href';
+import {HookApi, PluginApi} from '../../plugins/gr-plugin-types';
+import {notUndefined} from '../../../types/types';
type Callback = (value: any) => void;
interface ModuleInfo {
moduleName: string;
- // TODO(TS): Convert type to GrPlugin.
- plugin: any;
- pluginUrl: URL;
+ plugin: PluginApi;
+ pluginUrl?: URL;
type?: string;
- // TODO(TS): Convert type to GrDomHook.
- domHook: any;
+ domHook?: HookApi;
slot?: string;
}
@@ -36,8 +36,7 @@
slot?: string;
type?: string;
moduleName?: string;
- // TODO(TS): Convert type to GrDomHook.
- domHook?: any;
+ domHook?: HookApi;
}
export class GrPluginEndpoints {
@@ -71,7 +70,7 @@
}
}
- _getOrCreateModuleInfo(plugin: any, opts: Options): ModuleInfo {
+ _getOrCreateModuleInfo(plugin: PluginApi, opts: Options): ModuleInfo {
const {endpoint, slot, type, moduleName, domHook} = opts;
const existingModule = this._endpoints
.get(endpoint!)!
@@ -105,7 +104,7 @@
* 'change-list-header'. These plugins are then fetched by prefix to determine
* which endpoints to dynamically add to the page.
*/
- registerModule(plugin: any, opts: Options) {
+ registerModule(plugin: PluginApi, opts: Options) {
const endpoint = opts.endpoint!;
const dynamicEndpoint = opts.dynamicEndpoint;
if (dynamicEndpoint) {
@@ -173,7 +172,9 @@
if (!modulesData.length) {
return [];
}
- return Array.from(new Set(modulesData.map(m => m.pluginUrl)));
+ return Array.from(new Set(modulesData.map(m => m.pluginUrl))).filter(
+ notUndefined
+ );
}
importUrl(pluginUrl: URL) {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.ts
similarity index 61%
rename from polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.js
rename to polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.ts
index 6b8f73e..31254ce 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.ts
@@ -1,5 +1,3 @@
-import {appContext} from '../../../services/app-context.js';
-
/**
* @license
* Copyright (C) 2019 The Android Open Source Project
@@ -16,41 +14,58 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {importHref} from '../../../scripts/import-href.js';
+import {appContext} from '../../../services/app-context';
+import {importHref} from '../../../scripts/import-href';
import {
PLUGIN_LOADING_TIMEOUT_MS,
PRELOADED_PROTOCOL,
getPluginNameFromUrl,
-} from './gr-api-utils.js';
-import {Plugin} from './gr-public-js-api.js';
-import {getBaseUrl} from '../../../utils/url-util.js';
-import {getPluginEndpoints} from './gr-plugin-endpoints.js';
+} from './gr-api-utils';
+import {Plugin} from './gr-public-js-api';
+import {getBaseUrl} from '../../../utils/url-util';
+import {getPluginEndpoints} from './gr-plugin-endpoints';
+import {PluginApi} from '../../plugins/gr-plugin-types';
+import {ReportingService} from '../../../services/gr-reporting/gr-reporting';
+import {hasOwnProperty} from '../../../utils/common-util';
-/**
- * @enum {string}
- */
-const PluginState = {
- /**
- * State that indicates the plugin is pending to be loaded.
- */
- PENDING: 'PENDING',
+enum PluginState {
+ /** State that indicates the plugin is pending to be loaded. */
+ PENDING = 'PENDING',
+ /** State that indicates the plugin is already loaded. */
+ LOADED = 'LOADED',
+ /** State that indicates the plugin failed to load. */
+ LOAD_FAILED = 'LOAD_FAILED',
+}
- /**
- * State that indicates the plugin is already loaded.
- */
- LOADED: 'LOADED',
+interface PluginObject {
+ name: string;
+ url: string;
+ state: PluginState;
+ plugin: PluginApi | null;
+}
- /**
- * State that indicates the plugin is already loaded.
- */
- PRE_LOADED: 'PRE_LOADED',
+interface PluginOption {
+ sync?: boolean;
+}
- /**
- * State that indicates the plugin failed to load.
- */
- LOAD_FAILED: 'LOAD_FAILED',
+export interface PluginOptionMap {
+ [path: string]: PluginOption;
+}
+
+type GerritScriptElement = HTMLScriptElement & {
+ __importElement: HTMLScriptElement;
};
+type PluginCallback = (plugin: PluginApi) => void;
+
+interface PluginCallbackMap {
+ [name: string]: PluginCallback;
+}
+
+interface GerritGlobal {
+ _preloadedPlugins?: PluginCallbackMap;
+}
+
// Prefix for any unrecognized plugin urls.
// Url should match following patterns:
// /plugins/PLUGINNAME/static/SCRIPTNAME.(html|js)
@@ -71,20 +86,17 @@
* Check plugin status and if all plugins loaded.
*/
export class PluginLoader {
- constructor() {
- this._pluginListLoaded = false;
+ _pluginListLoaded = false;
- /** @type {Map<string,PluginLoader.PluginObject>} */
- this._plugins = new Map();
+ _plugins = new Map<string, PluginObject>();
- this._reporting = null;
+ _reporting: ReportingService | null = null;
- // Promise that resolves when all plugins loaded
- this._loadingPromise = null;
+ // Promise that resolves when all plugins loaded
+ _loadingPromise: Promise<void> | null = null;
- // Resolver to resolve _loadingPromise once all plugins loaded
- this._loadingResolver = null;
- }
+ // Resolver to resolve _loadingPromise once all plugins loaded
+ _loadingResolver: (() => void) | null = null;
_getReporting() {
if (!this._reporting) {
@@ -95,22 +107,15 @@
/**
* Use the plugin name or use the full url if not recognized.
- *
- * @see gr-api-utils#getPluginNameFromUrl
- * @param {string|URL} url
*/
- _getPluginKeyFromUrl(url) {
- return getPluginNameFromUrl(url) ||
- `${UNKNOWN_PLUGIN_PREFIX}${url}`;
+ _getPluginKeyFromUrl(url: string) {
+ return getPluginNameFromUrl(url) || `${UNKNOWN_PLUGIN_PREFIX}${url}`;
}
/**
* Load multiple plugins with certain options.
- *
- * @param {Array<string>} plugins
- * @param {Object<string, PluginLoader.PluginOption>} opts
*/
- loadPlugins(plugins = [], opts = {}) {
+ loadPlugins(plugins: string[] = [], opts: PluginOptionMap = {}) {
this._pluginListLoaded = true;
plugins.forEach(path => {
@@ -143,7 +148,7 @@
});
}
- _isPathEndsWith(url, suffix) {
+ _isPathEndsWith(url: string | URL, suffix: string) {
if (!(url instanceof URL)) {
try {
url = new URL(url);
@@ -166,19 +171,29 @@
return installedPlugins;
}
- install(callback, opt_version, opt_src) {
+ install(
+ callback: (plugin: PluginApi) => void,
+ version?: string,
+ src?: string
+ ) {
// HTML import polyfill adds __importElement pointing to the import tag.
- const script = document.currentScript &&
- (document.currentScript.__importElement || document.currentScript);
- let src = opt_src || (script && script.src);
- if (!src || src.startsWith('data:')) {
+ const gerritScript = document.currentScript as GerritScriptElement | null;
+ const script = gerritScript?.__importElement ?? gerritScript;
+ if (!src && script && script.src) {
+ src = script.src;
+ }
+ if ((!src || src.startsWith('data:')) && script && script.baseURI) {
src = script && script.baseURI;
}
-
- if (opt_version && opt_version !== API_VERSION) {
- this._failToLoad(`Plugin ${src} install error: only version ` +
- API_VERSION + ' is supported in PolyGerrit. ' + opt_version +
- ' was given.', src);
+ if (!src) {
+ this._failToLoad('Failed to determine src.');
+ return;
+ }
+ if (version && version !== API_VERSION) {
+ this._failToLoad(
+ `Plugin ${src} install error: only version ${API_VERSION} is supported in PolyGerrit. ${version} was given.`,
+ src
+ );
return;
}
@@ -231,21 +246,23 @@
return `Timeout when loading plugins: ${pendingPlugins.join(',')}`;
}
- _failToLoad(message, pluginUrl) {
+ _failToLoad(message: string, pluginUrl?: string) {
// Show an alert with the error
- document.dispatchEvent(new CustomEvent('show-alert', {
- detail: {
- message: `Plugin install error: ${message} from ${pluginUrl}`,
- },
- }));
- this._updatePluginState(pluginUrl, PluginState.LOAD_FAILED);
+ document.dispatchEvent(
+ new CustomEvent('show-alert', {
+ detail: {
+ message: `Plugin install error: ${message} from ${pluginUrl}`,
+ },
+ })
+ );
+ if (pluginUrl) this._updatePluginState(pluginUrl, PluginState.LOAD_FAILED);
this._checkIfCompleted();
}
- _updatePluginState(pluginUrl, state) {
+ _updatePluginState(pluginUrl: string, state: PluginState): PluginObject {
const key = this._getPluginKeyFromUrl(pluginUrl);
if (this._plugins.has(key)) {
- this._plugins.get(key).state = state;
+ this._plugins.get(key)!.state = state;
} else {
// Plugin is not recorded for some reason.
console.info(`Plugin loaded separately: ${pluginUrl}`);
@@ -256,10 +273,10 @@
plugin: null,
});
}
- return this._plugins.get(key);
+ return this._plugins.get(key)!;
}
- _pluginInstalled(url, plugin) {
+ _pluginInstalled(url: string, plugin: PluginApi) {
const pluginObj = this._updatePluginState(url, PluginState.LOADED);
pluginObj.plugin = plugin;
this._getReporting().pluginLoaded(plugin.getPluginName() || url);
@@ -268,20 +285,22 @@
}
installPreloadedPlugins() {
- if (!window.Gerrit || !window.Gerrit._preloadedPlugins) { return; }
- const Gerrit = window.Gerrit;
- for (const name in Gerrit._preloadedPlugins) {
- if (!Gerrit._preloadedPlugins.hasOwnProperty(name)) { continue; }
+ const Gerrit = window.Gerrit as GerritGlobal;
+ if (!Gerrit || !Gerrit._preloadedPlugins) {
+ return;
+ }
+ for (const name of Object.keys(Gerrit._preloadedPlugins)) {
const callback = Gerrit._preloadedPlugins[name];
this.install(callback, API_VERSION, PRELOADED_PROTOCOL + name);
}
}
- isPluginPreloaded(pathOrUrl) {
+ isPluginPreloaded(pathOrUrl: string) {
const url = this._urlFor(pathOrUrl);
const name = getPluginNameFromUrl(url);
- if (name && window.Gerrit._preloadedPlugins) {
- return window.Gerrit._preloadedPlugins.hasOwnProperty(name);
+ const Gerrit = window.Gerrit as GerritGlobal;
+ if (name && Gerrit._preloadedPlugins) {
+ return hasOwnProperty(Gerrit._preloadedPlugins, name);
} else {
return false;
}
@@ -289,10 +308,8 @@
/**
* Checks if given plugin path/url is enabled or not.
- *
- * @param {string} pathOrUrl
*/
- isPluginEnabled(pathOrUrl) {
+ isPluginEnabled(pathOrUrl: string) {
const url = this._urlFor(pathOrUrl);
if (this.isPluginPreloaded(url)) return true;
const key = this._getPluginKeyFromUrl(url);
@@ -301,54 +318,48 @@
/**
* Returns the plugin object with a given url.
- *
- * @param {string} pathOrUrl
*/
- getPlugin(pathOrUrl) {
- const key = this._getPluginKeyFromUrl(this._urlFor(pathOrUrl));
+ getPlugin(pathOrUrl: string) {
+ const url = this._urlFor(pathOrUrl);
+ const key = this._getPluginKeyFromUrl(url);
return this._plugins.get(key);
}
/**
* Checks if given plugin path/url is loaded or not.
- *
- * @param {string} pathOrUrl
*/
- isPluginLoaded(pathOrUrl) {
+ isPluginLoaded(pathOrUrl: string): boolean {
const url = this._urlFor(pathOrUrl);
const key = this._getPluginKeyFromUrl(url);
- return this._plugins.has(key) ?
- this._plugins.get(key).state === PluginState.LOADED :
- false;
+ return this._plugins.has(key)
+ ? this._plugins.get(key)!.state === PluginState.LOADED
+ : false;
}
- _importHtmlPlugin(pluginUrl, opts = {}) {
+ _importHtmlPlugin(pluginUrl: string, opts: PluginOption = {}) {
const urlWithAP = this._urlFor(pluginUrl, window.ASSETS_PATH);
const urlWithoutAP = this._urlFor(pluginUrl);
- let onerror = null;
+ let onerror = undefined;
if (urlWithAP !== urlWithoutAP) {
onerror = () => this._loadHtmlPlugin(urlWithoutAP, opts.sync);
}
this._loadHtmlPlugin(urlWithAP, opts.sync, onerror);
}
- _loadHtmlPlugin(url, sync, onerror) {
+ _loadHtmlPlugin(url: string, sync?: boolean, onerror?: (e: Event) => void) {
if (!onerror) {
onerror = () => {
this._failToLoad(`${url} import error`, url);
};
}
- importHref(
- url, () => {},
- onerror,
- !sync);
+ importHref(url, () => {}, onerror, !sync);
}
- _loadJsPlugin(pluginUrl) {
+ _loadJsPlugin(pluginUrl: string) {
const urlWithAP = this._urlFor(pluginUrl, window.ASSETS_PATH);
const urlWithoutAP = this._urlFor(pluginUrl);
- let onerror = null;
+ let onerror = undefined;
if (urlWithAP !== urlWithoutAP) {
onerror = () => this._createScriptTag(urlWithoutAP);
}
@@ -356,7 +367,7 @@
this._createScriptTag(urlWithAP, onerror);
}
- _createScriptTag(url, onerror) {
+ _createScriptTag(url: string, onerror?: OnErrorEventHandler) {
if (!onerror) {
onerror = () => this._failToLoad(`${url} load error`, url);
}
@@ -372,23 +383,24 @@
return document.body.appendChild(el);
}
- _urlFor(pathOrUrl, assetsPath) {
- if (!pathOrUrl) {
- return pathOrUrl;
- }
-
+ _urlFor(pathOrUrl: string, assetsPath?: string): string {
// theme is per host, should always load from assetsPath
- const isThemeFile = pathOrUrl.endsWith('static/gerrit-theme.html') ||
+ const isThemeFile =
+ pathOrUrl.endsWith('static/gerrit-theme.html') ||
pathOrUrl.endsWith('static/gerrit-theme.js');
const shouldTryLoadFromAssetsPathFirst = !isThemeFile && assetsPath;
- if (pathOrUrl.startsWith(PRELOADED_PROTOCOL) ||
- pathOrUrl.startsWith('http')) {
+ if (
+ pathOrUrl.startsWith(PRELOADED_PROTOCOL) ||
+ pathOrUrl.startsWith('http')
+ ) {
// Plugins are loaded from another domain or preloaded.
- if (pathOrUrl.includes(location.host) &&
- shouldTryLoadFromAssetsPathFirst) {
+ if (
+ pathOrUrl.includes(location.host) &&
+ shouldTryLoadFromAssetsPathFirst &&
+ assetsPath
+ ) {
// if is loading from host server, try replace with cdn when assetsPath provided
- return pathOrUrl
- .replace(location.origin, assetsPath);
+ return pathOrUrl.replace(location.origin, assetsPath);
}
return pathOrUrl;
}
@@ -397,7 +409,7 @@
pathOrUrl = '/' + pathOrUrl;
}
- if (shouldTryLoadFromAssetsPathFirst) {
+ if (shouldTryLoadFromAssetsPathFirst && assetsPath) {
return assetsPath + pathOrUrl;
}
@@ -412,39 +424,25 @@
return Promise.resolve();
}
if (!this._loadingPromise) {
- let timerId;
- this._loadingPromise =
- Promise.race([
- new Promise(resolve => this._loadingResolver = resolve),
- new Promise((_, reject) => timerId = setTimeout(
- () => {
- reject(new Error(this._timeout()));
- }, PLUGIN_LOADING_TIMEOUT_MS)),
- ]).finally(() => {
- if (timerId) clearTimeout(timerId);
- });
+ // TODO(TS): Should be a number, but TS thinks that is must be some weird
+ // NodeJS.Timeout object.
+ let timerId: any;
+ this._loadingPromise = Promise.race([
+ new Promise(resolve => (this._loadingResolver = resolve)),
+ new Promise(
+ (_, reject) =>
+ (timerId = setTimeout(() => {
+ reject(new Error(this._timeout()));
+ }, PLUGIN_LOADING_TIMEOUT_MS))
+ ),
+ ]).finally(() => {
+ if (timerId) clearTimeout(timerId);
+ }) as Promise<void>;
}
return this._loadingPromise;
}
}
-/**
- * @typedef {{
- * name:string,
- * url:string,
- * state:PluginState,
- * plugin:Object
- * }}
- */
-PluginLoader.PluginObject;
-
-/**
- * @typedef {{
- * sync:boolean,
- * }}
- */
-PluginLoader.PluginOption;
-
// TODO(dmfilippov): Convert to service and add to appContext
export let pluginLoader = new PluginLoader();
export function _testOnly_resetPluginLoader() {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
deleted file mode 100644
index 6c3ccc9..0000000
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
+++ /dev/null
@@ -1,446 +0,0 @@
-/**
- * @license
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import {getBaseUrl} from '../../../utils/url-util.js';
-import {getSharedApiEl} from '../../../utils/dom-util.js';
-import {GrAttributeHelper} from '../../plugins/gr-attribute-helper/gr-attribute-helper.js';
-import {GrChangeActionsInterface} from './gr-change-actions-js-api.js';
-import {GrChangeReplyInterface} from './gr-change-reply-js-api.js';
-import {GrDomHooksManager} from '../../plugins/gr-dom-hooks/gr-dom-hooks.js';
-import {GrThemeApi} from '../../plugins/gr-theme-api/gr-theme-api.js';
-import {GrPopupInterface} from '../../plugins/gr-popup-interface/gr-popup-interface.js';
-import {GrAdminApi} from '../../plugins/gr-admin-api/gr-admin-api.js';
-import {GrAnnotationActionsInterface} from './gr-annotation-actions-js-api.js';
-import {GrChangeMetadataApi} from '../../plugins/gr-change-metadata-api/gr-change-metadata-api.js';
-import {GrEventHelper} from '../../plugins/gr-event-helper/gr-event-helper.js';
-import {GrPluginRestApi} from './gr-plugin-rest-api.js';
-import {GrRepoApi} from '../../plugins/gr-repo-api/gr-repo-api.js';
-import {GrSettingsApi} from '../../plugins/gr-settings-api/gr-settings-api.js';
-import {GrStylesApi} from '../../plugins/gr-styles-api/gr-styles-api.js';
-import {GrPluginActionContext} from './gr-plugin-action-context.js';
-import {getPluginEndpoints} from './gr-plugin-endpoints.js';
-
-import {
- PRELOADED_PROTOCOL,
- getPluginNameFromUrl,
- send,
-} from './gr-api-utils.js';
-import {GrReporintJsApi} from './gr-reporting-js-api.js';
-
-const PANEL_ENDPOINTS_MAPPING = {
- CHANGE_SCREEN_BELOW_COMMIT_INFO_BLOCK: 'change-view-integration',
- CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK: 'change-metadata-item',
-};
-
-/**
- * Plugin-provided custom components can affect content in extension
- * points using one of following methods:
- * - DECORATE: custom component is set with `content` attribute and may
- * decorate (e.g. style) DOM element.
- * - REPLACE: contents of extension point are replaced with the custom
- * component.
- * - STYLE: custom component is a shared styles module that is inserted
- * into the extension point.
- */
-const EndpointType = {
- DECORATE: 'decorate',
- REPLACE: 'replace',
- STYLE: 'style',
-};
-
-export class Plugin {
- constructor(opt_url) {
- this._domHooks = new GrDomHooksManager(this);
-
- if (!opt_url) {
- console.warn(
- 'Plugin not being loaded from /plugins base path.',
- 'Unable to determine name.'
- );
- return this;
- }
- this.deprecated = {
- _loadedGwt: deprecatedAPI._loadedGwt.bind(this),
- install: deprecatedAPI.install.bind(this),
- onAction: deprecatedAPI.onAction.bind(this),
- panel: deprecatedAPI.panel.bind(this),
- popup: deprecatedAPI.popup.bind(this),
- screen: deprecatedAPI.screen.bind(this),
- settingsScreen: deprecatedAPI.settingsScreen.bind(this),
- };
-
- this._url = new URL(opt_url);
- this._name = getPluginNameFromUrl(this._url);
- this.sharedApiElement = getSharedApiEl();
- }
-
- getPluginName() {
- return this._name;
- }
-
- registerStyleModule(endpoint, moduleName) {
- getPluginEndpoints().registerModule(this, {
- endpoint,
- type: EndpointType.STYLE,
- moduleName,
- });
- }
-
- /**
- * Registers an endpoint for the plugin.
- */
- registerCustomComponent(endpointName, opt_moduleName, opt_options) {
- return this._registerCustomComponent(
- endpointName,
- opt_moduleName,
- opt_options
- );
- }
-
- /**
- * Registers a dynamic endpoint for the plugin.
- *
- * Dynamic plugins are registered by specific prefix, such as
- * 'change-list-header'.
- */
- registerDynamicCustomComponent(endpointName, opt_moduleName, opt_options) {
- const fullEndpointName = `${endpointName}-${this.getPluginName()}`;
- return this._registerCustomComponent(
- fullEndpointName,
- opt_moduleName,
- opt_options,
- endpointName
- );
- }
-
- _registerCustomComponent(
- endpoint,
- opt_moduleName,
- opt_options,
- dynamicEndpoint
- ) {
- const type =
- opt_options && opt_options.replace
- ? EndpointType.REPLACE
- : EndpointType.DECORATE;
- const slot = (opt_options && opt_options.slot) || '';
- const domHook = this._domHooks.getDomHook(endpoint, opt_moduleName);
- const moduleName = opt_moduleName || domHook.getModuleName();
- getPluginEndpoints().registerModule(this, {
- slot,
- endpoint,
- type,
- moduleName,
- domHook,
- dynamicEndpoint,
- });
- return domHook.getPublicAPI();
- }
-
- /**
- * Returns instance of DOM hook API for endpoint. Creates a placeholder
- * element for the first call.
- */
- hook(endpointName, opt_options) {
- return this.registerCustomComponent(endpointName, undefined, opt_options);
- }
-
- getServerInfo() {
- return document.createElement('gr-rest-api-interface').getConfig();
- }
-
- on(eventName, callback) {
- this.sharedApiElement.addEventCallback(eventName, callback);
- }
-
- url(opt_path) {
- const relPath = '/plugins/' + this._name + (opt_path || '/');
- const sameOriginPath = window.location.origin + `${getBaseUrl()}${relPath}`;
- if (window.location.origin === this._url.origin) {
- // Plugin loaded from the same origin as gr-app, getBaseUrl in effect.
- return sameOriginPath;
- } else if (this._url.protocol === PRELOADED_PROTOCOL) {
- // Plugin is preloaded, load plugin with ASSETS_PATH or location.origin
- return window.ASSETS_PATH
- ? `${window.ASSETS_PATH}${relPath}`
- : sameOriginPath;
- } else {
- // Plugin loaded from assets bundle, expect assets placed along with it.
- return this._url.href.split('/plugins/' + this._name)[0] + relPath;
- }
- }
-
- screenUrl(opt_screenName) {
- const origin = location.origin;
- const base = getBaseUrl();
- const tokenPart = opt_screenName ? '/' + opt_screenName : '';
- return `${origin}${base}/x/${this.getPluginName()}${tokenPart}`;
- }
-
- _send(method, url, opt_callback, opt_payload) {
- return send(method, this.url(url), opt_callback, opt_payload);
- }
-
- get(url, opt_callback) {
- console.warn('.get() is deprecated! Use .restApi().get()');
- return this._send('GET', url, opt_callback);
- }
-
- post(url, payload, opt_callback) {
- console.warn('.post() is deprecated! Use .restApi().post()');
- return this._send('POST', url, opt_callback, payload);
- }
-
- put(url, payload, opt_callback) {
- console.warn('.put() is deprecated! Use .restApi().put()');
- return this._send('PUT', url, opt_callback, payload);
- }
-
- delete(url, opt_callback) {
- console.warn('.delete() is deprecated! Use plugin.restApi().delete()');
- return this.restApi()
- .delete(this.url(url))
- .then(res => {
- if (opt_callback) {
- opt_callback(res);
- }
- return res;
- });
- }
-
- annotationApi() {
- return new GrAnnotationActionsInterface(this);
- }
-
- changeActions() {
- return new GrChangeActionsInterface(
- this,
- this.sharedApiElement.getElement(
- this.sharedApiElement.Element.CHANGE_ACTIONS
- )
- );
- }
-
- changeReply() {
- return new GrChangeReplyInterface(this, this.sharedApiElement);
- }
-
- reporting() {
- return new GrReporintJsApi(this);
- }
-
- theme() {
- return new GrThemeApi(this);
- }
-
- project() {
- return new GrRepoApi(this);
- }
-
- changeMetadata() {
- return new GrChangeMetadataApi(this);
- }
-
- admin() {
- return new GrAdminApi(this);
- }
-
- settings() {
- return new GrSettingsApi(this);
- }
-
- styles() {
- return new GrStylesApi();
- }
-
- /**
- * To make REST requests for plugin-provided endpoints, use
- *
- * @example
- * const pluginRestApi = plugin.restApi(plugin.url());
- *
- * @param {string=} opt_prefix url for subsequent .get(), .post() etc requests.
- */
- restApi(opt_prefix) {
- return new GrPluginRestApi(opt_prefix);
- }
-
- attributeHelper(element) {
- return new GrAttributeHelper(element);
- }
-
- eventHelper(element) {
- return new GrEventHelper(element);
- }
-
- popup(moduleName) {
- if (typeof moduleName !== 'string') {
- console.error('.popup(element) deprecated, use .popup(moduleName)!');
- return;
- }
- const api = new GrPopupInterface(this, moduleName);
- return api.open();
- }
-
- panel() {
- console.error(
- '.panel() is deprecated! ' + 'Use registerCustomComponent() instead.'
- );
- }
-
- settingsScreen() {
- console.error(
- '.settingsScreen() is deprecated! ' + 'Use .settings() instead.'
- );
- }
-
- screen(screenName, opt_moduleName) {
- if (opt_moduleName && typeof opt_moduleName !== 'string') {
- console.error(
- '.screen(pattern, callback) deprecated, use ' +
- '.screen(screenName, opt_moduleName)!'
- );
- return;
- }
- return this.registerCustomComponent(
- this._getScreenName(screenName),
- opt_moduleName
- );
- }
-
- _getScreenName(screenName) {
- return `${this.getPluginName()}-screen-${screenName}`;
- }
-}
-
-// TODO: should be removed soon after all core plugins moved away from it.
-const deprecatedAPI = {
- _loadedGwt: () => {},
-
- install() {
- console.info('Installing deprecated APIs is deprecated!');
- for (const method in this.deprecated) {
- if (method === 'install') continue;
- this[method] = this.deprecated[method];
- }
- },
-
- popup(el) {
- console.warn(
- 'plugin.deprecated.popup() is deprecated, '
- + 'use plugin.popup() insted!'
- );
- if (!el) {
- throw new Error('Popup contents not found');
- }
- const api = new GrPopupInterface(this);
- api.open().then(api => api._getElement().appendChild(el));
- return api;
- },
-
- onAction(type, action, callback) {
- console.warn(
- 'plugin.deprecated.onAction() is deprecated,' +
- ' use plugin.changeActions() instead!'
- );
- if (type !== 'change' && type !== 'revision') {
- console.warn(`${type} actions are not supported.`);
- return;
- }
- this.on('showchange', (change, revision) => {
- const details = this.changeActions().getActionDetails(action);
- if (!details) {
- console.warn(
- `${this.getPluginName()} onAction error: ${action} not found!`
- );
- return;
- }
- this.changeActions().addTapListener(details.__key, () => {
- callback(new GrPluginActionContext(this, details, change, revision));
- });
- });
- },
-
- screen(pattern, callback) {
- console.warn(
- 'plugin.deprecated.screen is deprecated,'
- + ' use plugin.screen instead!'
- );
- if (pattern instanceof RegExp) {
- console.error(
- 'deprecated.screen() does not support RegExp. ' +
- 'Please use strings for patterns.'
- );
- return;
- }
- this.hook(this._getScreenName(pattern)).onAttached(el => {
- el.style.display = 'none';
- callback({
- body: el,
- token: el.token,
- onUnload: () => {},
- setTitle: () => {},
- setWindowTitle: () => {},
- show: () => {
- el.style.display = 'initial';
- },
- });
- });
- },
-
- settingsScreen(path, menu, callback) {
- console.warn('.settingsScreen() is deprecated! Use .settings() instead.');
- const hook = this.settings().title(menu)
- .token(path)
- .module('div')
- .build();
- hook.onAttached(el => {
- el.style.display = 'none';
- const body = el.querySelector('div');
- callback({
- body,
- onUnload: () => {},
- setTitle: () => {},
- setWindowTitle: () => {},
- show: () => {
- el.style.display = 'initial';
- },
- });
- });
- },
-
- panel(extensionpoint, callback) {
- console.warn(
- '.panel() is deprecated! ' + 'Use registerCustomComponent() instead.'
- );
- const endpoint = PANEL_ENDPOINTS_MAPPING[extensionpoint];
- if (!endpoint) {
- console.warn(`.panel ${extensionpoint} not supported!`);
- return;
- }
- this.hook(endpoint).onAttached(el =>
- callback({
- body: el,
- p: {
- CHANGE_INFO: el.change,
- REVISION_INFO: el.revision,
- },
- onUnload: () => {},
- })
- );
- },
-};
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.ts
new file mode 100644
index 0000000..c6b3485
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.ts
@@ -0,0 +1,506 @@
+/**
+ * @license
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {getBaseUrl} from '../../../utils/url-util';
+import {getSharedApiEl} from '../../../utils/dom-util';
+import {GrAttributeHelper} from '../../plugins/gr-attribute-helper/gr-attribute-helper';
+import {GrChangeActionsInterface} from './gr-change-actions-js-api';
+import {GrChangeReplyInterface} from './gr-change-reply-js-api';
+import {GrDomHooksManager} from '../../plugins/gr-dom-hooks/gr-dom-hooks';
+import {GrThemeApi} from '../../plugins/gr-theme-api/gr-theme-api';
+import {GrPopupInterface} from '../../plugins/gr-popup-interface/gr-popup-interface';
+import {GrAdminApi} from '../../plugins/gr-admin-api/gr-admin-api';
+import {GrAnnotationActionsInterface} from './gr-annotation-actions-js-api';
+import {GrChangeMetadataApi} from '../../plugins/gr-change-metadata-api/gr-change-metadata-api';
+import {GrEventHelper} from '../../plugins/gr-event-helper/gr-event-helper';
+import {GrPluginRestApi} from './gr-plugin-rest-api';
+import {GrRepoApi} from '../../plugins/gr-repo-api/gr-repo-api';
+import {GrSettingsApi} from '../../plugins/gr-settings-api/gr-settings-api';
+import {GrStylesApi} from '../../plugins/gr-styles-api/gr-styles-api';
+import {GrPluginActionContext} from './gr-plugin-action-context';
+import {getPluginEndpoints} from './gr-plugin-endpoints';
+
+import {PRELOADED_PROTOCOL, getPluginNameFromUrl, send} from './gr-api-utils';
+import {GrReporintJsApi} from './gr-reporting-js-api';
+import {
+ EventType,
+ HookApi,
+ PanelInfo,
+ PluginApi,
+ PluginDeprecatedApi,
+ RegisterOptions,
+ SettingsInfo,
+ TargetElement,
+} from '../../plugins/gr-plugin-types';
+import {ActionInfo, RequestPayload} from '../../../types/common';
+import {HttpMethod} from '../../../constants/constants';
+import {JsApiService} from './gr-js-api-types';
+import {GrChangeActions} from '../../../services/services/gr-rest-api/gr-rest-api';
+
+const PANEL_ENDPOINTS_MAPPING = {
+ CHANGE_SCREEN_BELOW_COMMIT_INFO_BLOCK: 'change-view-integration',
+ CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK: 'change-metadata-item',
+};
+
+/**
+ * Plugin-provided custom components can affect content in extension
+ * points using one of following methods:
+ * - DECORATE: custom component is set with `content` attribute and may
+ * decorate (e.g. style) DOM element.
+ * - REPLACE: contents of extension point are replaced with the custom
+ * component.
+ * - STYLE: custom component is a shared styles module that is inserted
+ * into the extension point.
+ */
+enum EndpointType {
+ DECORATE = 'decorate',
+ REPLACE = 'replace',
+ STYLE = 'style',
+}
+
+const PLUGIN_NAME_NOT_SET = 'NULL';
+
+export type SendCallback = (response: unknown) => void;
+
+export class Plugin implements PluginApi {
+ readonly deprecated: PluginDeprecatedApi;
+
+ readonly _url?: URL;
+
+ private _domHooks: GrDomHooksManager;
+
+ private readonly _name: string = PLUGIN_NAME_NOT_SET;
+
+ // TODO(TS): Change type to GrJsApiInterface
+ private readonly sharedApiElement: JsApiService;
+
+ constructor(url?: string) {
+ this.deprecated = {
+ _loadedGwt: () => {},
+ install: () => this.deprecatedInstall(),
+ onAction: (
+ type: string,
+ action: string,
+ callback: (ctx: GrPluginActionContext) => void
+ ) => this.deprecatedOnAction(type, action, callback),
+ panel: (extensionpoint: string, callback: (panel: PanelInfo) => void) =>
+ this.deprecatedPanel(extensionpoint, callback),
+ popup: (el: Element) => this.deprecatedPopup(el),
+ screen: (pattern: string, callback: (settings: SettingsInfo) => void) =>
+ this.deprecatedScreen(pattern, callback),
+ settingsScreen: (
+ path: string,
+ menu: string,
+ callback: (settings: SettingsInfo) => void
+ ) => this.deprecatedSettingsScreen(path, menu, callback),
+ };
+ this.sharedApiElement = getSharedApiEl();
+ this._domHooks = new GrDomHooksManager(this);
+
+ if (!url) {
+ console.warn(
+ 'Plugin not being loaded from /plugins base path.',
+ 'Unable to determine name.'
+ );
+ return this;
+ }
+
+ this._url = new URL(url);
+ this._name = getPluginNameFromUrl(this._url) ?? 'NULL';
+ }
+
+ getPluginName() {
+ return this._name;
+ }
+
+ registerStyleModule(endpoint: string, moduleName: string) {
+ getPluginEndpoints().registerModule(this, {
+ endpoint,
+ type: EndpointType.STYLE,
+ moduleName,
+ });
+ }
+
+ /**
+ * Registers an endpoint for the plugin.
+ */
+ registerCustomComponent(
+ endpointName: string,
+ moduleName?: string,
+ options?: RegisterOptions
+ ): HookApi {
+ return this._registerCustomComponent(endpointName, moduleName, options);
+ }
+
+ /**
+ * Registers a dynamic endpoint for the plugin.
+ *
+ * Dynamic plugins are registered by specific prefix, such as
+ * 'change-list-header'.
+ */
+ registerDynamicCustomComponent(
+ endpointName: string,
+ moduleName?: string,
+ options?: RegisterOptions
+ ): HookApi {
+ const fullEndpointName = `${endpointName}-${this.getPluginName()}`;
+ return this._registerCustomComponent(
+ fullEndpointName,
+ moduleName,
+ options,
+ endpointName
+ );
+ }
+
+ _registerCustomComponent(
+ endpoint: string,
+ moduleName?: string,
+ options?: RegisterOptions,
+ dynamicEndpoint?: string
+ ): HookApi {
+ const type =
+ options && options.replace ? EndpointType.REPLACE : EndpointType.DECORATE;
+ const slot = (options && options.slot) || '';
+ const domHook = this._domHooks.getDomHook(endpoint, moduleName);
+ moduleName = moduleName || domHook.getModuleName();
+ getPluginEndpoints().registerModule(this, {
+ slot,
+ endpoint,
+ type,
+ moduleName,
+ domHook,
+ dynamicEndpoint,
+ });
+ return domHook;
+ }
+
+ /**
+ * Returns instance of DOM hook API for endpoint. Creates a placeholder
+ * element for the first call.
+ */
+ hook(endpointName: string, options?: RegisterOptions) {
+ return this.registerCustomComponent(endpointName, undefined, options);
+ }
+
+ getServerInfo() {
+ return document.createElement('gr-rest-api-interface').getConfig();
+ }
+
+ on(eventName: EventType, callback: (...args: any[]) => any) {
+ this.sharedApiElement.addEventCallback(eventName, callback);
+ }
+
+ url(path?: string) {
+ if (!this._url) throw new Error('plugin url not set');
+ const relPath = '/plugins/' + this._name + (path || '/');
+ const sameOriginPath = window.location.origin + `${getBaseUrl()}${relPath}`;
+ if (window.location.origin === this._url.origin) {
+ // Plugin loaded from the same origin as gr-app, getBaseUrl in effect.
+ return sameOriginPath;
+ } else if (this._url.protocol === PRELOADED_PROTOCOL) {
+ // Plugin is preloaded, load plugin with ASSETS_PATH or location.origin
+ return window.ASSETS_PATH
+ ? `${window.ASSETS_PATH}${relPath}`
+ : sameOriginPath;
+ } else {
+ // Plugin loaded from assets bundle, expect assets placed along with it.
+ return this._url.href.split('/plugins/' + this._name)[0] + relPath;
+ }
+ }
+
+ screenUrl(screenName?: string) {
+ const origin = location.origin;
+ const base = getBaseUrl();
+ const tokenPart = screenName ? '/' + screenName : '';
+ return `${origin}${base}/x/${this.getPluginName()}${tokenPart}`;
+ }
+
+ _send(
+ method: HttpMethod,
+ url: string,
+ callback?: SendCallback,
+ payload?: RequestPayload
+ ) {
+ return send(method, this.url(url), callback, payload);
+ }
+
+ get(url: string, callback?: SendCallback) {
+ console.warn('.get() is deprecated! Use .restApi().get()');
+ return this._send(HttpMethod.GET, url, callback);
+ }
+
+ post(url: string, payload: RequestPayload, callback?: SendCallback) {
+ console.warn('.post() is deprecated! Use .restApi().post()');
+ return this._send(HttpMethod.POST, url, callback, payload);
+ }
+
+ put(url: string, payload: RequestPayload, callback?: SendCallback) {
+ console.warn('.put() is deprecated! Use .restApi().put()');
+ return this._send(HttpMethod.PUT, url, callback, payload);
+ }
+
+ delete(url: string, callback?: SendCallback) {
+ console.warn('.delete() is deprecated! Use plugin.restApi().delete()');
+ return this.restApi()
+ .delete(this.url(url))
+ .then(res => {
+ if (callback) callback(res);
+ return res;
+ });
+ }
+
+ annotationApi() {
+ return new GrAnnotationActionsInterface(this);
+ }
+
+ changeActions() {
+ return new GrChangeActionsInterface(
+ this,
+ (this.sharedApiElement.getElement(
+ TargetElement.CHANGE_ACTIONS
+ ) as unknown) as GrChangeActions
+ );
+ }
+
+ changeReply() {
+ return new GrChangeReplyInterface(this, this.sharedApiElement);
+ }
+
+ reporting() {
+ return new GrReporintJsApi(this);
+ }
+
+ theme() {
+ return new GrThemeApi(this);
+ }
+
+ project() {
+ return new GrRepoApi(this);
+ }
+
+ changeMetadata() {
+ return new GrChangeMetadataApi(this);
+ }
+
+ admin() {
+ return new GrAdminApi(this);
+ }
+
+ settings() {
+ return new GrSettingsApi(this);
+ }
+
+ styles() {
+ return new GrStylesApi();
+ }
+
+ /**
+ * To make REST requests for plugin-provided endpoints, use
+ *
+ * @example
+ * const pluginRestApi = plugin.restApi(plugin.url());
+ * @param prefix url for subsequent .get(), .post() etc requests.
+ */
+ restApi(prefix?: string) {
+ return new GrPluginRestApi(prefix);
+ }
+
+ attributeHelper(element: HTMLElement) {
+ return new GrAttributeHelper(element);
+ }
+
+ eventHelper(element: HTMLElement) {
+ return new GrEventHelper(element);
+ }
+
+ popup(moduleName: string) {
+ if (typeof moduleName !== 'string') {
+ console.error('.popup(element) deprecated, use .popup(moduleName)!');
+ return;
+ }
+ const api = new GrPopupInterface(this, moduleName);
+ return api.open();
+ }
+
+ panel() {
+ console.error(
+ '.panel() is deprecated! ' + 'Use registerCustomComponent() instead.'
+ );
+ }
+
+ settingsScreen() {
+ console.error(
+ '.settingsScreen() is deprecated! ' + 'Use .settings() instead.'
+ );
+ }
+
+ screen(screenName: string, moduleName?: string) {
+ if (moduleName && typeof moduleName !== 'string') {
+ console.error(
+ '.screen(pattern, callback) deprecated, use ' +
+ '.screen(screenName, moduleName)!'
+ );
+ return;
+ }
+ return this.registerCustomComponent(
+ this._getScreenName(screenName),
+ moduleName
+ );
+ }
+
+ _getScreenName(screenName: string) {
+ return `${this.getPluginName()}-screen-${screenName}`;
+ }
+
+ // !!! DEPRECATED !!!
+ // All methods below are deprecated!
+ // TODO: should be removed soon after all core plugins moved away from it.
+
+ deprecatedInstall() {
+ console.info('Installing deprecated APIs is deprecated!');
+ const deprecatedThis = (this as unknown) as PluginDeprecatedApi;
+ deprecatedThis._loadedGwt = this.deprecated._loadedGwt;
+ deprecatedThis.onAction = this.deprecated.onAction;
+ deprecatedThis.panel = this.deprecated.panel;
+ deprecatedThis.popup = this.deprecated.popup;
+ deprecatedThis.screen = this.deprecated.screen;
+ deprecatedThis.settingsScreen = this.deprecated.settingsScreen;
+ }
+
+ deprecatedPopup(el: Element): GrPopupInterface {
+ console.warn(
+ 'plugin.deprecated.popup() is deprecated, ' + 'use plugin.popup() insted!'
+ );
+ if (!el) {
+ throw new Error('Popup contents not found');
+ }
+ const api = new GrPopupInterface(this);
+ api.open().then(api => {
+ const popupEl = api._getElement();
+ if (!popupEl) {
+ throw new Error('Popup element not found');
+ }
+ popupEl.appendChild(el);
+ });
+ return api;
+ }
+
+ deprecatedOnAction(
+ type: string,
+ action: string,
+ callback: (ctx: GrPluginActionContext) => void
+ ) {
+ console.warn(
+ 'plugin.deprecated.onAction() is deprecated,' +
+ ' use plugin.changeActions() instead!'
+ );
+ if (type !== 'change' && type !== 'revision') {
+ console.warn(`${type} actions are not supported.`);
+ return;
+ }
+ this.on(EventType.SHOW_CHANGE, (change, revision) => {
+ const details: ActionInfo = this.changeActions().getActionDetails(action);
+ if (!details) {
+ console.warn(
+ `${this.getPluginName()} onAction error: ${action} not found!`
+ );
+ return;
+ }
+ if (!details.__key) {
+ console.warn(
+ `${this.getPluginName()} onAction error: ${action} has no key!`
+ );
+ return;
+ }
+ this.changeActions().addTapListener(details.__key, () => {
+ callback(new GrPluginActionContext(this, details, change, revision));
+ });
+ });
+ }
+
+ deprecatedScreen(
+ pattern: string,
+ callback: (settings: SettingsInfo) => void
+ ) {
+ console.warn(
+ 'plugin.deprecated.screen is deprecated,' + ' use plugin.screen instead!'
+ );
+ this.hook(this._getScreenName(pattern)).onAttached(el => {
+ el.style.display = 'none';
+ callback({
+ body: el,
+ token: el.token,
+ onUnload: () => {},
+ setTitle: () => {},
+ setWindowTitle: () => {},
+ show: () => {
+ el.style.display = 'initial';
+ },
+ });
+ });
+ }
+
+ deprecatedSettingsScreen(
+ path: string,
+ menu: string,
+ callback: (settings: SettingsInfo) => void
+ ) {
+ console.warn('.settingsScreen() is deprecated! Use .settings() instead.');
+ const hook = this.settings().title(menu).token(path).module('div').build();
+ hook.onAttached(el => {
+ el.style.display = 'none';
+ const body = el.querySelector('div');
+ if (!body) return;
+ callback({
+ body,
+ onUnload: () => {},
+ setTitle: () => {},
+ setWindowTitle: () => {},
+ show: () => {
+ el.style.display = 'initial';
+ },
+ });
+ });
+ }
+
+ deprecatedPanel(
+ extensionpoint: string,
+ callback: (panel: PanelInfo) => void
+ ) {
+ console.warn(
+ '.panel() is deprecated! ' + 'Use registerCustomComponent() instead.'
+ );
+ let endpoint;
+ for (const [key, value] of Object.entries(PANEL_ENDPOINTS_MAPPING)) {
+ if (key === extensionpoint) endpoint = value;
+ }
+ if (!endpoint) {
+ console.warn(`.panel ${extensionpoint} not supported!`);
+ return;
+ }
+ this.hook(endpoint).onAttached(el =>
+ callback({
+ body: el,
+ p: {
+ CHANGE_INFO: el.change,
+ REVISION_INFO: el.revision,
+ },
+ onUnload: () => {},
+ })
+ );
+ }
+}
diff --git a/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader.js b/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader.js
index 2d1d3ac..f6bb0e3 100644
--- a/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader.js
+++ b/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader.js
@@ -19,6 +19,7 @@
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
import {htmlTemplate} from './gr-lib-loader_html.js';
+import {EventType} from '../../plugins/gr-plugin-types.js';
// preloaded in PolyGerritIndexHtml.soy
const HLJS_PATH = 'bower_components/highlightjs/highlight.min.js';
@@ -79,7 +80,7 @@
_onHLJSLibLoaded() {
const lib = this._getHighlightLib();
this._hljsState.loading = false;
- this.$.jsAPI.handleEvent(this.$.jsAPI.EventType.HIGHLIGHTJS_LOADED, {
+ this.$.jsAPI.handleEvent(EventType.HIGHLIGHTJS_LOADED, {
hljs: lib,
});
for (const cb of this._hljsState.callbacks) {
diff --git a/polygerrit-ui/app/services/gr-auth/gr-auth_test.js b/polygerrit-ui/app/services/gr-auth/gr-auth_test.js
index 432518c..80938ad 100644
--- a/polygerrit-ui/app/services/gr-auth/gr-auth_test.js
+++ b/polygerrit-ui/app/services/gr-auth/gr-auth_test.js
@@ -144,8 +144,7 @@
assert.equal(auth.status, Auth.STATUS.AUTHED);
clock.tick(1000 * 10000);
fakeFetch.returns(Promise.resolve({status: 403}));
- const emitStub = sinon.stub();
- appContext.eventEmitter.emit = emitStub;
+ const emitStub = sinon.stub(appContext.eventEmitter, 'emit');
auth.authCheck().then(authed2 => {
assert.isFalse(authed2);
assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
@@ -162,8 +161,7 @@
assert.equal(auth.status, Auth.STATUS.AUTHED);
clock.tick(1000 * 10000);
fakeFetch.returns(Promise.reject(new Error('random error')));
- const emitStub = sinon.stub();
- appContext.eventEmitter.emit = emitStub;
+ const emitStub = sinon.stub(appContext.eventEmitter, 'emit');
auth.authCheck().then(authed2 => {
assert.isFalse(authed2);
assert.isTrue(emitStub.called);
@@ -180,8 +178,7 @@
assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
clock.tick(1000 * 10000);
fakeFetch.returns(Promise.resolve({status: 204}));
- const emitStub = sinon.stub();
- appContext.eventEmitter.emit = emitStub;
+ const emitStub = sinon.stub(appContext.eventEmitter, 'emit');
auth.authCheck().then(authed2 => {
assert.isTrue(authed2);
assert.isFalse(emitStub.called);
@@ -198,8 +195,7 @@
assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
clock.tick(1000 * 10000);
fakeFetch.returns(Promise.reject(new Error('random error')));
- const emitStub = sinon.stub();
- appContext.eventEmitter.emit = emitStub;
+ const emitStub = sinon.stub(appContext.eventEmitter, 'emit');
auth.authCheck().then(authed2 => {
assert.isFalse(authed2);
assert.isFalse(emitStub.called);
diff --git a/polygerrit-ui/app/services/gr-event-interface/gr-event-interface_test.js b/polygerrit-ui/app/services/gr-event-interface/gr-event-interface_test.js
index 6d0ab7b..32590e0 100644
--- a/polygerrit-ui/app/services/gr-event-interface/gr-event-interface_test.js
+++ b/polygerrit-ui/app/services/gr-event-interface/gr-event-interface_test.js
@@ -18,60 +18,58 @@
import '../../test/common-test-setup-karma.js';
import '../../elements/shared/gr-js-api-interface/gr-js-api-interface.js';
import {EventEmitter} from './gr-event-interface_impl.js';
-import {_testOnly_initGerritPluginApi} from '../../elements/shared/gr-js-api-interface/gr-gerrit.js';
const basicFixture = fixtureFromElement('gr-js-api-interface');
-const pluginApi = _testOnly_initGerritPluginApi();
-
suite('gr-event-interface tests', () => {
+ let gerrit;
setup(() => {
-
+ gerrit = window.Gerrit;
});
suite('test on Gerrit', () => {
setup(() => {
basicFixture.instantiate();
- pluginApi.removeAllListeners();
+ gerrit.removeAllListeners();
});
test('communicate between plugin and Gerrit', done => {
const eventName = 'test-plugin-event';
let p;
- pluginApi.on(eventName, e => {
+ gerrit.on(eventName, e => {
assert.equal(e.value, 'test');
assert.equal(e.plugin, p);
done();
});
- pluginApi.install(plugin => {
+ gerrit.install(plugin => {
p = plugin;
- pluginApi.emit(eventName, {value: 'test', plugin});
+ gerrit.emit(eventName, {value: 'test', plugin});
}, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
});
test('listen on events from core', done => {
const eventName = 'test-plugin-event';
- pluginApi.on(eventName, e => {
+ gerrit.on(eventName, e => {
assert.equal(e.value, 'test');
done();
});
- pluginApi.emit(eventName, {value: 'test'});
+ gerrit.emit(eventName, {value: 'test'});
});
test('communicate across plugins', done => {
const eventName = 'test-plugin-event';
- pluginApi.install(plugin => {
- pluginApi.on(eventName, e => {
+ gerrit.install(plugin => {
+ gerrit.on(eventName, e => {
assert.equal(e.plugin.getPluginName(), 'testB');
done();
});
}, '0.1',
'http://test.com/plugins/testA/static/testA.js');
- pluginApi.install(plugin => {
- pluginApi.emit(eventName, {plugin});
+ gerrit.install(plugin => {
+ gerrit.emit(eventName, {plugin});
}, '0.1',
'http://test.com/plugins/testB/static/testB.js');
});
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 9b86ae9..de96db3 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
@@ -81,11 +81,6 @@
export type ErrorCallback = (response?: Response | null, err?: Error) => void;
export type CancelConditionCallback = () => boolean;
-export enum ApiElement {
- CHANGE_ACTIONS = 'changeactions',
- REPLY_DIALOG = 'replydialog',
-}
-
// TODO(TS): remove when GrReplyDialog converted to typescript
export interface GrReplyDialog {
getLabelValue(label: string): string;
@@ -130,17 +125,6 @@
getActionDetails(actionName: string): ActionInfo;
}
-export interface RestApiTagNameMap {
- [ApiElement.REPLY_DIALOG]: GrReplyDialog;
- [ApiElement.CHANGE_ACTIONS]: GrChangeActions;
-}
-
-export interface JsApiService {
- getElement<K extends keyof RestApiTagNameMap>(
- elementKey: K
- ): RestApiTagNameMap[K];
-}
-
export interface GetDiffCommentsOutput {
baseComments: CommentInfo[];
comments: CommentInfo[];
diff --git a/polygerrit-ui/app/test/common-test-setup.js b/polygerrit-ui/app/test/common-test-setup.js
index 19465a3..004daf7 100644
--- a/polygerrit-ui/app/test/common-test-setup.js
+++ b/polygerrit-ui/app/test/common-test-setup.js
@@ -18,10 +18,10 @@
// TODO(dmfilippov): remove bundled-polymer.js imports when the following issue
// https://github.com/Polymer/polymer-resin/issues/9 is resolved.
import '../scripts/bundled-polymer.js';
-import './test-app-context-init.js';
import 'polymer-resin/standalone/polymer-resin.js';
import '@polymer/iron-test-helpers/iron-test-helpers.js';
import './test-router.js';
+import {_testOnlyInitAppContext} from './test-app-context-init';
import {_testOnly_resetPluginLoader} from '../elements/shared/gr-js-api-interface/gr-plugin-loader.js';
import {_testOnlyResetRestApi} from '../elements/shared/gr-js-api-interface/gr-plugin-rest-api.js';
import {_testOnlyResetGrRestApiSharedObjects} from '../elements/shared/gr-rest-api-interface/gr-rest-api-interface.js';
@@ -30,6 +30,7 @@
import {_testOnly_getShortcutManagerInstance} from '../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.js';
import sinon from 'sinon/pkg/sinon-esm.js';
import {safeTypesBridge} from '../utils/safe-types-util.js';
+import {_testOnly_initGerritPluginApi} from '../elements/shared/gr-js-api-interface/gr-gerrit.js';
window.sinon = sinon;
security.polymer_resin.install({
@@ -65,6 +66,8 @@
// The following calls is nessecary to avoid influence of previously executed
// tests.
TestKeyboardShortcutBinder.push();
+ _testOnlyInitAppContext();
+ _testOnly_initGerritPluginApi();
const mgr = _testOnly_getShortcutManagerInstance();
assert.equal(mgr.activeHosts.size, 0);
assert.equal(mgr.listeners.size, 0);
diff --git a/polygerrit-ui/app/test/test-app-context-init.js b/polygerrit-ui/app/test/test-app-context-init.js
index 88232cd3..68e68f0 100644
--- a/polygerrit-ui/app/test/test-app-context-init.js
+++ b/polygerrit-ui/app/test/test-app-context-init.js
@@ -20,13 +20,15 @@
import {grReportingMock} from '../services/gr-reporting/gr-reporting_mock.js';
import {appContext} from '../services/app-context.js';
-initAppContext();
+export function _testOnlyInitAppContext() {
+ initAppContext();
-function setMock(serviceName, setupMock) {
- Object.defineProperty(appContext, serviceName, {
- get() {
- return setupMock;
- },
- });
+ function setMock(serviceName, setupMock) {
+ Object.defineProperty(appContext, serviceName, {
+ get() {
+ return setupMock;
+ },
+ });
+ }
+ setMock('reportingService', grReportingMock);
}
-setMock('reportingService', grReportingMock);
diff --git a/polygerrit-ui/app/test/test-utils.js b/polygerrit-ui/app/test/test-utils.js
index e1eadef..12a10d6 100644
--- a/polygerrit-ui/app/test/test-utils.js
+++ b/polygerrit-ui/app/test/test-utils.js
@@ -123,3 +123,17 @@
}
return change;
}
+
+/**
+ * Forcing an opacity of 0 onto the ironOverlayBackdrop is required, because
+ * otherwise the backdrop stays around in the DOM for too long waiting for
+ * an animation to finish. This could be considered to be moved to a
+ * common-test-setup file.
+ */
+export function createIronOverlayBackdropStyleEl() {
+ const ironOverlayBackdropStyleEl = document.createElement('style');
+ document.head.appendChild(ironOverlayBackdropStyleEl);
+ ironOverlayBackdropStyleEl.sheet.insertRule(
+ 'body { --iron-overlay-backdrop-opacity: 0; }');
+ return ironOverlayBackdropStyleEl;
+}
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts
index dcbfcca..f453c4a 100644
--- a/polygerrit-ui/app/types/common.ts
+++ b/polygerrit-ui/app/types/common.ts
@@ -367,6 +367,8 @@
* https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#action-info
*/
export interface ActionInfo {
+ __key?: string;
+ __url?: string;
method?: HttpMethod; // Most actions use POST, PUT or DELETE to cause state changes.
label?: string; // Short title to display to a user describing the action
title?: string; // Longer text to display describing the action
diff --git a/polygerrit-ui/app/types/types.ts b/polygerrit-ui/app/types/types.ts
index b54c67a..ece3d3a 100644
--- a/polygerrit-ui/app/types/types.ts
+++ b/polygerrit-ui/app/types/types.ts
@@ -16,6 +16,10 @@
*/
import {Side} from '../constants/constants';
+export function notUndefined<T>(x: T | undefined): x is T {
+ return x !== undefined;
+}
+
export interface CoverageRange {
type: CoverageType;
side: Side;
diff --git a/polygerrit-ui/app/utils/dom-util.ts b/polygerrit-ui/app/utils/dom-util.ts
index 0def6ad..ae6d616 100644
--- a/polygerrit-ui/app/utils/dom-util.ts
+++ b/polygerrit-ui/app/utils/dom-util.ts
@@ -17,6 +17,7 @@
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
+import {JsApiService} from '../elements/shared/gr-js-api-interface/gr-js-api-types';
/**
* Event emitted from polymer elements.
@@ -236,18 +237,19 @@
}
// shared API element
-// TODO: once gr-js-api-interface moved to ts
-// use GrJsApiInterface instead
-let _sharedApiEl: Element;
+// TODO: Make this a proper service singleton. Move into AppContext?
+let _sharedApiEl: JsApiService;
/**
* Retrieves the shared API element.
* We want to keep a single instance of API element instead of
* creating multiple elements.
*/
-export function getSharedApiEl() {
+export function getSharedApiEl(): JsApiService {
if (!_sharedApiEl) {
- _sharedApiEl = document.createElement('gr-js-api-interface');
+ _sharedApiEl = (document.createElement(
+ 'gr-js-api-interface'
+ ) as unknown) as JsApiService;
}
return _sharedApiEl;
}