plugin.deprecated.onAction partial support
As a part of "stepping stone" to simplify migration of plugins that
target GWT UI primarily. Provides self.deprecated.install method that
moves deprecated API such as `self.deprecated.popup()` and
`self.deprecated.onAction()` to `self.popup()` and `self.onAction()`
respectively.
``` js
Gerrit.install(plugin => {
if (Polymer) {
// Promote deprecated APIs to default locations.
// Not recommended, only as a part of migrating GWT plugins to PG UI.
plugin.deprecated.install();
}
plugin.onAction('change', 'find-owners', (context) => {
console.log(context.change);
console.log(context.revision);
});
});
```
Provides partial support for self.onAction with methods:
- popup(element)
- hide()
- refresh()
- textfield()
- br()
- msg(text)
- div(...elements)
- button(label, callbacks)
- checkbox()
- label(checkbox, title)
- call(payload, onSuccess)
Populates plugin-provided change revision actions and puts them in
overflow menu.
Feature: Issue 5329
Change-Id: Id4528de9e48469d67904cfda8d1c5a22b15e8311
This commit is contained in:
@@ -323,6 +323,7 @@
|
||||
observers: [
|
||||
'_actionsChanged(actions.*, revisionActions.*, _additionalActions.*, ' +
|
||||
'editLoaded, editBasedOnCurrentPatchSet, change)',
|
||||
'_changeOrPatchNumChanged(changeNum, patchNum)',
|
||||
],
|
||||
|
||||
listeners: {
|
||||
@@ -353,6 +354,10 @@
|
||||
});
|
||||
},
|
||||
|
||||
_changeOrPatchNumChanged() {
|
||||
this.reload();
|
||||
},
|
||||
|
||||
addActionButton(type, label) {
|
||||
if (type !== ActionType.CHANGE && type !== ActionType.REVISION) {
|
||||
throw Error(`Invalid action type: ${type}`);
|
||||
@@ -433,6 +438,14 @@
|
||||
}
|
||||
},
|
||||
|
||||
getActionDetails(action) {
|
||||
if (this.revisionActions[action]) {
|
||||
return this.revisionActions[action];
|
||||
} else if (this.actions[action]) {
|
||||
return this.actions[action];
|
||||
}
|
||||
},
|
||||
|
||||
_indexOfActionButtonWithKey(key) {
|
||||
for (let i = 0; i < this._additionalActions.length; i++) {
|
||||
if (this._additionalActions[i].__key === key) {
|
||||
@@ -605,11 +618,26 @@
|
||||
const result = [];
|
||||
const values = this._getValuesFor(
|
||||
type === ActionType.CHANGE ? ChangeActions : RevisionActions);
|
||||
const pluginActions = [];
|
||||
for (const a in actions) {
|
||||
if (!values.includes(a)) { continue; }
|
||||
if (!actions.hasOwnProperty(a)) {
|
||||
continue;
|
||||
}
|
||||
actions[a].__key = a;
|
||||
actions[a].__type = type;
|
||||
actions[a].__primary = primaryActionKeys.includes(a);
|
||||
// Plugin actions always contain ~ in the key.
|
||||
if (a.indexOf('~') !== -1) {
|
||||
pluginActions.push(actions[a]);
|
||||
// Add server-side provided plugin actions to overflow menu.
|
||||
this._overflowActions.push({
|
||||
type,
|
||||
key: a,
|
||||
});
|
||||
continue;
|
||||
} else if (!values.includes(a)) {
|
||||
continue;
|
||||
}
|
||||
if (actions[a].label === 'Delete') {
|
||||
// This label is common within change and revision actions. Make it
|
||||
// more explicit to the user.
|
||||
@@ -618,7 +646,6 @@
|
||||
}
|
||||
}
|
||||
// Triggers a re-render by ensuring object inequality.
|
||||
// TODO(andybons): Polyfill for Object.assign.
|
||||
result.push(Object.assign({}, actions[a]));
|
||||
}
|
||||
|
||||
@@ -631,7 +658,7 @@
|
||||
// Triggers a re-render by ensuring object inequality.
|
||||
return Object.assign({}, a);
|
||||
});
|
||||
return result.concat(additionalActions);
|
||||
return result.concat(additionalActions).concat(pluginActions);
|
||||
},
|
||||
|
||||
_computeLoadingLabel(action) {
|
||||
@@ -668,7 +695,8 @@
|
||||
e.preventDefault();
|
||||
const el = Polymer.dom(e).localTarget;
|
||||
const key = el.getAttribute('data-action-key');
|
||||
if (key.startsWith(ADDITIONAL_ACTION_KEY_PREFIX)) {
|
||||
if (key.startsWith(ADDITIONAL_ACTION_KEY_PREFIX) ||
|
||||
key.indexOf('~') !== -1) {
|
||||
this.fire(`${key}-tap`, {node: el});
|
||||
return;
|
||||
}
|
||||
@@ -677,6 +705,14 @@
|
||||
},
|
||||
|
||||
_handleOveflowItemTap(e) {
|
||||
e.preventDefault();
|
||||
const el = Polymer.dom(e).localTarget;
|
||||
const key = e.detail.action.__key;
|
||||
if (key.startsWith(ADDITIONAL_ACTION_KEY_PREFIX) ||
|
||||
key.indexOf('~') !== -1) {
|
||||
this.fire(`${key}-tap`, {node: el});
|
||||
return;
|
||||
}
|
||||
this._handleAction(e.detail.action.__type, e.detail.action.__key);
|
||||
},
|
||||
|
||||
|
||||
@@ -108,6 +108,32 @@ limitations under the License.
|
||||
assert.isFalse(element._shouldHideActions({base: ['test']}, false));
|
||||
});
|
||||
|
||||
test('plugin actions', () => {
|
||||
element.revisionActions = {
|
||||
'plugin~action': {},
|
||||
};
|
||||
assert.isOk(element.revisionActions['plugin~action']);
|
||||
});
|
||||
|
||||
test('not supported actions are filtered out', () => {
|
||||
element.revisionActions = {
|
||||
followup: {
|
||||
},
|
||||
};
|
||||
assert.equal(element.querySelectorAll('section gr-button').length, 0);
|
||||
});
|
||||
|
||||
test('getActionDetails', () => {
|
||||
element.revisionActions = Object.assign({
|
||||
'plugin~action': {},
|
||||
}, element.revisionActions);
|
||||
assert.isUndefined(element.getActionDetails('rubbish'));
|
||||
assert.strictEqual(element.revisionActions['plugin~action'],
|
||||
element.getActionDetails('plugin~action'));
|
||||
assert.strictEqual(element.revisionActions['rebase'],
|
||||
element.getActionDetails('rebase'));
|
||||
});
|
||||
|
||||
test('hide revision action', done => {
|
||||
flush(() => {
|
||||
const buttonEl = element.$$('[data-action-key="submit"]');
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
function GrChangeActionsInterface(el) {
|
||||
function GrChangeActionsInterface(plugin, el) {
|
||||
this.plugin = plugin;
|
||||
this._el = el;
|
||||
this.RevisionActions = el.RevisionActions;
|
||||
this.ChangeActions = el.ChangeActions;
|
||||
@@ -73,5 +74,10 @@
|
||||
this._el.setActionButtonProp(key, 'enabled', enabled);
|
||||
};
|
||||
|
||||
GrChangeActionsInterface.prototype.getActionDetails = function(action) {
|
||||
return this._el.getActionDetails(action) ||
|
||||
this._el.getActionDetails(this.plugin.getPluginName() + '~' + action);
|
||||
};
|
||||
|
||||
window.GrChangeActionsInterface = GrChangeActionsInterface;
|
||||
})(window);
|
||||
|
||||
@@ -31,5 +31,6 @@ limitations under the License.
|
||||
<script src="gr-change-reply-js-api.js"></script>
|
||||
<script src="gr-js-api-interface.js"></script>
|
||||
<script src="gr-plugin-endpoints.js"></script>
|
||||
<script src="gr-plugin-action-context.js"></script>
|
||||
<script src="gr-public-js-api.js"></script>
|
||||
</dom-module>
|
||||
|
||||
@@ -352,6 +352,13 @@ limitations under the License.
|
||||
assert.isOk(plugin.attributeHelper());
|
||||
});
|
||||
|
||||
test('deprecated.install', () => {
|
||||
plugin.deprecated.install();
|
||||
assert.strictEqual(plugin.popup, plugin.deprecated.popup);
|
||||
assert.strictEqual(plugin.onAction, plugin.deprecated.onAction);
|
||||
assert.notStrictEqual(plugin.install, plugin.deprecated.install);
|
||||
});
|
||||
|
||||
suite('test plugin with base url', () => {
|
||||
setup(() => {
|
||||
sandbox.stub(Gerrit.BaseUrlBehavior, 'getBaseUrl').returns('/r');
|
||||
@@ -398,5 +405,43 @@ limitations under the License.
|
||||
assert.isTrue(openStub.calledOnce);
|
||||
});
|
||||
});
|
||||
|
||||
suite('onAction', () => {
|
||||
let change;
|
||||
let revision;
|
||||
let actionDetails;
|
||||
|
||||
setup(() => {
|
||||
change = {};
|
||||
revision = {};
|
||||
actionDetails = {__key: 'some'};
|
||||
sandbox.stub(plugin, 'on').callsArgWith(1, change, revision);
|
||||
sandbox.stub(plugin, 'changeActions').returns({
|
||||
addTapListener: sandbox.stub().callsArg(1),
|
||||
getActionDetails: () => actionDetails,
|
||||
});
|
||||
});
|
||||
|
||||
test('returns GrPluginActionContext', () => {
|
||||
const stub = sandbox.stub();
|
||||
plugin.deprecated.onAction('change', 'foo', ctx => {
|
||||
assert.isTrue(ctx instanceof GrPluginActionContext);
|
||||
assert.strictEqual(ctx.change, change);
|
||||
assert.strictEqual(ctx.revision, revision);
|
||||
assert.strictEqual(ctx.action, actionDetails);
|
||||
assert.strictEqual(ctx.plugin, plugin);
|
||||
stub();
|
||||
});
|
||||
assert.isTrue(stub.called);
|
||||
});
|
||||
|
||||
test('other actions', () => {
|
||||
const stub = sandbox.stub();
|
||||
plugin.deprecated.onAction('project', 'foo', stub);
|
||||
plugin.deprecated.onAction('edit', 'foo', stub);
|
||||
plugin.deprecated.onAction('branch', 'foo', stub);
|
||||
assert.isFalse(stub.called);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// 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.
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
function GrPluginActionContext(plugin, action, change, revision) {
|
||||
this.action = action;
|
||||
this.plugin = plugin;
|
||||
this.change = change;
|
||||
this.revision = revision;
|
||||
this._popups = [];
|
||||
}
|
||||
|
||||
GrPluginActionContext.prototype.popup = function(element) {
|
||||
this._popups.push(this.plugin.deprecated.popup(element));
|
||||
};
|
||||
|
||||
GrPluginActionContext.prototype.hide = function() {
|
||||
for (const popupApi of this._popups) {
|
||||
popupApi.close();
|
||||
}
|
||||
this._popups.splice(0);
|
||||
};
|
||||
|
||||
GrPluginActionContext.prototype.refresh = function() {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
GrPluginActionContext.prototype.textfield = function() {
|
||||
return document.createElement('paper-input');
|
||||
};
|
||||
|
||||
GrPluginActionContext.prototype.br = function() {
|
||||
return document.createElement('br');
|
||||
};
|
||||
|
||||
GrPluginActionContext.prototype.msg = function(text) {
|
||||
const label = document.createElement('gr-label');
|
||||
Polymer.dom(label).appendChild(document.createTextNode(text));
|
||||
return label;
|
||||
};
|
||||
|
||||
GrPluginActionContext.prototype.div = function(...els) {
|
||||
const div = document.createElement('div');
|
||||
for (const el of els) {
|
||||
Polymer.dom(div).appendChild(el);
|
||||
}
|
||||
return div;
|
||||
};
|
||||
|
||||
GrPluginActionContext.prototype.button = function(label, callbacks) {
|
||||
const onClick = callbacks && callbacks.onclick;
|
||||
const button = document.createElement('gr-button');
|
||||
Polymer.dom(button).appendChild(document.createTextNode(label));
|
||||
if (onClick) {
|
||||
this.plugin.eventHelper(button).onTap(onClick);
|
||||
}
|
||||
return button;
|
||||
};
|
||||
|
||||
GrPluginActionContext.prototype.checkbox = function() {
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
return checkbox;
|
||||
};
|
||||
|
||||
GrPluginActionContext.prototype.label = function(checkbox, title) {
|
||||
return this.div(checkbox, this.msg(title));
|
||||
};
|
||||
|
||||
GrPluginActionContext.prototype.call = function(payload, onSuccess) {
|
||||
this.plugin._send(
|
||||
this.action.method, '/' + this.action.__key, onSuccess, payload);
|
||||
};
|
||||
|
||||
window.GrPluginActionContext = GrPluginActionContext;
|
||||
})(window);
|
||||
@@ -0,0 +1,127 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright (C) 2017 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.
|
||||
-->
|
||||
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-plugin-action-context</title>
|
||||
|
||||
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
|
||||
<script src="../../../bower_components/web-component-tester/browser.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-js-api-interface.html"/>
|
||||
|
||||
<script>void(0);</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-plugin-action-context tests', () => {
|
||||
let instance;
|
||||
let sandbox;
|
||||
let plugin;
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
Gerrit._setPluginsCount(1);
|
||||
Gerrit.install(p => { plugin = p; }, '0.1',
|
||||
'http://test.com/plugins/testplugin/static/test.js');
|
||||
instance = new GrPluginActionContext(plugin);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
test('popup() and hide()', () => {
|
||||
const popupApiStub = {
|
||||
close: sandbox.stub(),
|
||||
};
|
||||
sandbox.stub(plugin.deprecated, 'popup').returns(popupApiStub);
|
||||
const el = {};
|
||||
instance.popup(el);
|
||||
assert.isTrue(instance.plugin.deprecated.popup.calledWith(el));
|
||||
|
||||
instance.hide();
|
||||
assert.isTrue(popupApiStub.close.called);
|
||||
});
|
||||
|
||||
test('textfield', () => {
|
||||
assert.equal(instance.textfield().tagName, 'PAPER-INPUT');
|
||||
});
|
||||
|
||||
test('br', () => {
|
||||
assert.equal(instance.br().tagName, 'BR');
|
||||
});
|
||||
|
||||
test('msg', () => {
|
||||
const el = instance.msg('foobar');
|
||||
assert.equal(el.tagName, 'GR-LABEL');
|
||||
assert.equal(el.textContent, 'foobar');
|
||||
});
|
||||
|
||||
test('div', () => {
|
||||
const el1 = document.createElement('span');
|
||||
el1.textContent = 'foo';
|
||||
const el2 = document.createElement('div');
|
||||
el2.textContent = 'bar';
|
||||
const div = instance.div(el1, el2);
|
||||
assert.equal(div.tagName, 'DIV');
|
||||
assert.equal(div.textContent, 'foobar');
|
||||
});
|
||||
|
||||
test('button', () => {
|
||||
const clickStub = sandbox.stub();
|
||||
const button = instance.button('foo', {onclick: clickStub});
|
||||
MockInteractions.tap(button);
|
||||
flush(() => {
|
||||
assert.isTrue(clickStub.called);
|
||||
assert.equal(button.textContent, 'foo');
|
||||
});
|
||||
});
|
||||
|
||||
test('checkbox', () => {
|
||||
const el = instance.checkbox();
|
||||
assert.equal(el.tagName, 'INPUT');
|
||||
assert.equal(el.type, 'checkbox');
|
||||
});
|
||||
|
||||
test('label', () => {
|
||||
const fakeMsg = {};
|
||||
const fakeCheckbox = {};
|
||||
sandbox.stub(instance, 'div');
|
||||
sandbox.stub(instance, 'msg').returns(fakeMsg);
|
||||
instance.label(fakeCheckbox, 'foo');
|
||||
assert.isTrue(instance.div.calledWithExactly(fakeCheckbox, fakeMsg));
|
||||
});
|
||||
|
||||
test('call', () => {
|
||||
instance.action = {
|
||||
method: 'METHOD',
|
||||
__key: 'key',
|
||||
};
|
||||
sandbox.stub(plugin, '_send');
|
||||
const payload = {foo: 'foo'};
|
||||
const successStub = sandbox.stub();
|
||||
instance.call(payload, successStub);
|
||||
assert.isTrue(
|
||||
plugin._send.calledWith('METHOD', '/key', successStub, payload));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -78,7 +78,9 @@
|
||||
this._name = pathname.split('/')[2];
|
||||
|
||||
this.deprecated = {
|
||||
install: deprecatedAPI.install.bind(this),
|
||||
popup: deprecatedAPI.popup.bind(this),
|
||||
onAction: deprecatedAPI.onAction.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -180,8 +182,9 @@
|
||||
},
|
||||
|
||||
Plugin.prototype.changeActions = function() {
|
||||
return new GrChangeActionsInterface(Plugin._sharedAPIElement.getElement(
|
||||
Plugin._sharedAPIElement.Element.CHANGE_ACTIONS));
|
||||
return new GrChangeActionsInterface(this,
|
||||
Plugin._sharedAPIElement.getElement(
|
||||
Plugin._sharedAPIElement.Element.CHANGE_ACTIONS));
|
||||
};
|
||||
|
||||
Plugin.prototype.changeReply = function() {
|
||||
@@ -218,14 +221,41 @@
|
||||
return api.open();
|
||||
};
|
||||
|
||||
const deprecatedAPI = {};
|
||||
deprecatedAPI.popup = function(el) {
|
||||
console.warn('plugin.deprecated.popup() is deprecated!');
|
||||
if (!el) {
|
||||
throw new Error('Popup contents not found');
|
||||
}
|
||||
const api = new GrPopupInterface(this);
|
||||
api.open().then(api => api._getElement().appendChild(el));
|
||||
const deprecatedAPI = {
|
||||
install() {
|
||||
console.log('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);
|
||||
this.changeActions().addTapListener(details.__key, () => {
|
||||
callback(new GrPluginActionContext(this, details, change, revision));
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
const Gerrit = window.Gerrit || {};
|
||||
|
||||
Reference in New Issue
Block a user