Add rebase action with confirmation dialog
TODO in follow-up change
+ Autocomplete revisions/change numbers from the input
+ Consolidate button classes (it’s enough repeated code
that it’s becomming a nuisance).
+ Perhaps move confirmation dialog functions into
a behavior.
Change-Id: I382cc63591cd537dbe1d29a0451f392c1e77f287
diff --git a/polygerrit-ui/app/elements/gr-change-actions.html b/polygerrit-ui/app/elements/gr-change-actions.html
index 850ecb7..997bfb22 100644
--- a/polygerrit-ui/app/elements/gr-change-actions.html
+++ b/polygerrit-ui/app/elements/gr-change-actions.html
@@ -15,8 +15,11 @@
-->
<link rel="import" href="../bower_components/polymer/polymer.html">
+<link rel="import" href="../bower_components/iron-input/iron-input.html">
<link rel="import" href="../behaviors/rest-client-behavior.html">
<link rel="import" href="gr-ajax.html">
+<link rel="import" href="gr-confirm-rebase-dialog.html">
+<link rel="import" href="gr-overlay.html">
<link rel="import" href="gr-request.html">
<dom-module id="gr-change-actions">
@@ -27,6 +30,7 @@
}
.primary {
background-color: #448aff;
+ border-color: #448aff;
color: #fff;
}
button:before {
@@ -40,16 +44,22 @@
font: inherit;
padding: .5em .75em;
}
+ button[loading],
+ button[disabled] {
+ opacity: .75;
+ }
button[loading] {
cursor: wait;
- opacity: .5;
}
button[loading]:before {
content: attr(data-loading-label);
}
button[disabled] {
- background-color: #555961;
- opacity: .5;
+ cursor: default;
+ }
+ button[loading],
+ button[loading][disabled] {
+ cursor: wait;
}
</style>
<gr-ajax id="actionsXHR"
@@ -75,6 +85,13 @@
on-tap="_handleActionTap"></button>
</template>
</div>
+ <gr-overlay id="overlay" with-backdrop>
+ <gr-confirm-rebase-dialog id="confirmRebase"
+ class="confirmDialog"
+ on-confirm="_handleRebaseConfirm"
+ on-cancel="_handleConfirmDialogCancel"
+ hidden></gr-confirm-rebase-dialog>
+ </gr-overlay>
</template>
<script>
(function() {
@@ -88,6 +105,7 @@
// TODO(andybons): Add the rest of the revision actions.
var RevisionActions = {
+ REBASE: 'rebase',
SUBMIT: 'submit',
};
@@ -110,9 +128,7 @@
type: Boolean,
value: true,
},
- _revisionActions: {
- type: Object,
- },
+ _revisionActions: Object,
},
behaviors: [
@@ -131,7 +147,9 @@
},
_actionsChanged: function(actions, revisionActions) {
- this.hidden = revisionActions.submit == null &&
+ this.hidden =
+ revisionActions.rebase == null &&
+ revisionActions.submit == null &&
actions.abandon == null &&
actions.restore == null;
},
@@ -160,6 +178,7 @@
_computeLoadingLabel: function(action) {
return {
+ 'rebase': 'Rebasing...',
'submit': 'Submitting...',
}[action];
},
@@ -173,12 +192,45 @@
var el = Polymer.dom(e).rootTarget;
var key = el.getAttribute('data-action-key');
if (this._getValuesFor(RevisionActions).indexOf(key) > -1) {
+ if (key == RevisionActions.REBASE) {
+ this._showRebaseDialog();
+ return;
+ }
this._fireRevisionAction('/' + key, this._revisionActions[key]);
} else {
this._fireChangeAction('/' + key, this.actions[key]);
}
},
+ _handleConfirmDialogCancel: function() {
+ var dialogEls =
+ Polymer.dom(this.root).querySelectorAll('.confirmDialog');
+ for (var i = 0; i < dialogEls.length; i++) {
+ dialogEls[i].hidden = true;
+ }
+ this.$.overlay.close();
+ },
+
+ _handleRebaseConfirm: function() {
+ var payload = {};
+ var el = this.$.confirmRebase;
+ if (el.clearParent) {
+ // There is a subtle but important difference between setting the base
+ // to an empty string and omitting it entirely from the payload. An
+ // empty string implies that the parent should be cleared and the
+ // change should be rebased on top of the target branch. Leaving out
+ // the base implies that it should be rebased on top of its current
+ // parent.
+ payload.base = '';
+ } else if (el.base && el.base.length > 0) {
+ payload.base = el.base;
+ }
+ this.$.overlay.close();
+ el.hidden = false;
+ this._fireRevisionAction('/rebase', this._revisionActions.rebase,
+ payload);
+ },
+
_fireChangeAction: function(endpoint, action) {
this._send(action.method, {}, endpoint).then(
function() {
@@ -190,25 +242,33 @@
});
},
- _fireRevisionAction: function(endpoint, action) {
+ _fireRevisionAction: function(endpoint, action, opt_payload) {
var buttonEl = this.$$('[data-action-key="' + action.__key + '"]');
buttonEl.setAttribute('loading', true);
buttonEl.disabled = true;
+ function enableButton() {
+ buttonEl.removeAttribute('loading');
+ buttonEl.disabled = false;
+ }
- this._send(action.method, {}, endpoint, true).then(
+ this._send(action.method, opt_payload, endpoint, true).then(
function() {
this.fire('reload-change', null, {bubbles: false});
- buttonEl.setAttribute('loading', false);
- buttonEl.disabled = false;
+ enableButton();
}.bind(this)).catch(function(err) {
+ // TODO(andybons): Handle merge conflict (409 status);
alert('Oops. Something went wrong. Check the console and bug the ' +
'PolyGerrit team for assistance.');
- buttonEl.setAttribute('loading', false);
- buttonEl.disabled = false;
+ enableButton();
throw err;
});
},
+ _showRebaseDialog: function() {
+ this.$.confirmRebase.hidden = false;
+ this.$.overlay.open();
+ },
+
_send: function(method, payload, actionEndpoint, revisionAction) {
var xhr = document.createElement('gr-request');
this._xhrPromise = xhr.send({
diff --git a/polygerrit-ui/app/elements/gr-change-view.html b/polygerrit-ui/app/elements/gr-change-view.html
index 9275666..fb0ca27 100644
--- a/polygerrit-ui/app/elements/gr-change-view.html
+++ b/polygerrit-ui/app/elements/gr-change-view.html
@@ -281,7 +281,7 @@
actions="[[_change.actions]]"
change-num="[[_changeNum]]"
patch-num="[[_patchNum]]"
- on-reload-change="_reload"></gr-change-actions>
+ on-reload-change="_handleReloadChange"></gr-change-actions>
</div>
<div class="changeInfo-column commitMessage">
<h4>Commit message</h4>
@@ -596,6 +596,10 @@
}
},
+ _handleReloadChange: function() {
+ page.show(this._computeChangePath(this._changeNum));
+ },
+
_reload: function() {
var detailCompletes = this.$.detailXHR.generateRequest().completes;
this.$.commentsXHR.generateRequest();
diff --git a/polygerrit-ui/app/elements/gr-confirm-dialog.html b/polygerrit-ui/app/elements/gr-confirm-dialog.html
new file mode 100644
index 0000000..beea8d2
--- /dev/null
+++ b/polygerrit-ui/app/elements/gr-confirm-dialog.html
@@ -0,0 +1,97 @@
+<!--
+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.
+-->
+
+<link rel="import" href="../bower_components/polymer/polymer.html">
+
+<dom-module id="gr-confirm-dialog">
+ <template>
+ <style>
+ :host {
+ display: block;
+ }
+ .header {
+ border-bottom: 1px solid #ddd;
+ font-weight: bold;
+ }
+ .header,
+ .mainContent,
+ .footer {
+ padding: .5em .65em;
+ }
+ .footer {
+ display: flex;
+ justify-content: space-between;
+ }
+ button {
+ background-color: #f1f2f3;
+ border: 1px solid #aaa;
+ border-radius: 2px;
+ cursor: pointer;
+ font: inherit;
+ padding: .5em .75em;
+ }
+ .confirm {
+ background-color: #448aff;
+ border-color: #448aff;
+ color: #fff;
+ }
+ </style>
+ <div class="header"><content select=".header"></content></div>
+ <div class="mainContent"><content select=".main"></content></div>
+ <div class="footer">
+ <button class="confirm" on-tap="_handleConfirmTap">[[confirmLabel]]</button>
+ <button class="cancel" on-tap="_handleCancelTap">Cancel</button>
+ </div>
+ </template>
+ <script>
+ (function() {
+ 'use strict';
+
+ Polymer({
+ is: 'gr-confirm-dialog',
+
+ /**
+ * Fired when the confirm button is pressed.
+ *
+ * @event confirm
+ */
+
+ /**
+ * Fired when the cancel button is pressed.
+ *
+ * @event cancel
+ */
+
+ properties: {
+ confirmLabel: {
+ type: String,
+ value: 'Confirm',
+ }
+ },
+
+ _handleConfirmTap: function(e) {
+ e.preventDefault();
+ this.fire('confirm', null, {bubbles: false});
+ },
+
+ _handleCancelTap: function(e) {
+ e.preventDefault();
+ this.fire('cancel', null, {bubbles: false});
+ },
+ });
+ })();
+ </script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/gr-confirm-rebase-dialog.html b/polygerrit-ui/app/elements/gr-confirm-rebase-dialog.html
new file mode 100644
index 0000000..96c7188
--- /dev/null
+++ b/polygerrit-ui/app/elements/gr-confirm-rebase-dialog.html
@@ -0,0 +1,119 @@
+<!--
+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.
+-->
+
+<link rel="import" href="../bower_components/polymer/polymer.html">
+<link rel="import" href="gr-confirm-dialog.html">
+
+<dom-module id="gr-confirm-rebase-dialog">
+ <template>
+ <style>
+ :host {
+ display: block;
+ width: 30em;
+ }
+ :host([disabled]) {
+ opacity: .5;
+ pointer-events: none;
+ }
+ label {
+ cursor: pointer;
+ }
+ .parentRevisionContainer label,
+ .parentRevisionContainer input[type="text"] {
+ display: block;
+ font: inherit;
+ width: 100%;
+ }
+ .parentRevisionContainer label {
+ margin-bottom: .2em;
+ }
+ .clearParentContainer {
+ margin: .5em 0;
+ }
+ </style>
+ <gr-confirm-dialog
+ confirm-label="Rebase"
+ on-confirm="_handleConfirmTap"
+ on-cancel="_handleCancelTap">
+ <div class="header">Confirm rebase</div>
+ <div class="main">
+ <div class="parentRevisionContainer">
+ <label for="parentInput">
+ Parent revision (optional)
+ </label>
+ <input is="iron-input"
+ type="text"
+ id="parentInput"
+ bind-value="{{base}}"
+ placeholder="Change number">
+ </div>
+ <div class="clearParentContainer">
+ <input id="clearParent"
+ type="checkbox"
+ on-tap="_handleClearParentTap">
+ <label for="clearParent">
+ Rebase on top of current branch (clear parent revision).
+ </label>
+ </div>
+ </div>
+ </gr-confirm-dialog>
+ </template>
+ <script>
+ (function() {
+ 'use strict';
+
+ Polymer({
+ is: 'gr-confirm-rebase-dialog',
+
+ /**
+ * Fired when the confirm button is pressed.
+ *
+ * @event confirm
+ */
+
+ /**
+ * Fired when the cancel button is pressed.
+ *
+ * @event cancel
+ */
+
+ properties: {
+ base: String,
+ clearParent: Boolean,
+ },
+
+ _handleConfirmTap: function(e) {
+ e.preventDefault();
+ this.fire('confirm', null, {bubbles: false});
+ },
+
+ _handleCancelTap: function(e) {
+ e.preventDefault();
+ this.fire('cancel', null, {bubbles: false});
+ },
+
+ _handleClearParentTap: function(e) {
+ var clear = Polymer.dom(e).rootTarget.checked;
+ if (clear) {
+ this.base = '';
+ }
+ this.$.parentInput.disabled = clear;
+ this.clearParent = clear;
+ },
+ });
+ })();
+ </script>
+</dom-module>
diff --git a/polygerrit-ui/app/test/gr-change-actions-test.html b/polygerrit-ui/app/test/gr-change-actions-test.html
index 14dd010..3fc5ef1 100644
--- a/polygerrit-ui/app/test/gr-change-actions-test.html
+++ b/polygerrit-ui/app/test/gr-change-actions-test.html
@@ -78,30 +78,34 @@
')]}\'\n{}', // The response is not used by the element.
]
);
- });
- test('submit button shows', function(done) {
+ server.respondWith(
+ 'POST',
+ '/changes/42/revisions/2/rebase',
+ [
+ 200,
+ {'Content-Type': 'application/json'},
+ ')]}\'\n{}', // The response is not used by the element.
+ ]
+ );
+
element.changeNum = '42';
element.patchNum = '2';
element.reload();
server.respond();
+ });
+ test('submit and rebase buttons show', function(done) {
element.async(function() {
var buttonEls = Polymer.dom(element.root).querySelectorAll('button');
- assert.equal(buttonEls.length, 1);
+ assert.equal(buttonEls.length, 2);
assert.isFalse(element.hidden);
done();
}, 1);
});
test('submit change', function(done) {
- element.changeNum = '42';
- element.patchNum = '2';
- element.reload();
-
- server.respond();
-
element.async(function() {
var submitButton = element.$$('button[data-action-key="submit"]');
assert.ok(submitButton);
@@ -114,5 +118,38 @@
});
}, 1);
});
+
+ test('rebase change', function(done) {
+ element.async(function() {
+ var rebaseButton = element.$$('button[data-action-key="rebase"]');
+ MockInteractions.tap(rebaseButton);
+
+ element.$.confirmRebase.base = '1234';
+ element._handleRebaseConfirm();
+ server.respond();
+ var lastRequest = server.requests[server.requests.length - 1];
+ assert.equal(lastRequest.requestBody, '{"base":"1234"}');
+
+ element.$.confirmRebase.base = '';
+ element._handleRebaseConfirm();
+ server.respond();
+ lastRequest = server.requests[server.requests.length - 1];
+ assert.equal(lastRequest.requestBody, '{}');
+
+ element.$.confirmRebase.base = 'does not matter';
+ element.$.confirmRebase.clearParent = true;
+ element._handleRebaseConfirm();
+ server.respond();
+ lastRequest = server.requests[server.requests.length - 1];
+ assert.equal(lastRequest.requestBody, '{"base":""}');
+
+ // Upon each request success it should fire the reload-change event.
+ var numEvents = 0;
+ element.addEventListener('reload-change', function(e) {
+ if (++numEvents == 3) { done(); }
+ });
+ }, 1);
+ });
+
});
</script>
diff --git a/polygerrit-ui/app/test/gr-confirm-dialog-test.html b/polygerrit-ui/app/test/gr-confirm-dialog-test.html
new file mode 100644
index 0000000..b61bb17
--- /dev/null
+++ b/polygerrit-ui/app/test/gr-confirm-dialog-test.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-confirm-dialog</title>
+
+<script src="../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../bower_components/web-component-tester/browser.js"></script>
+
+<link rel="import" href="../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../elements/gr-confirm-dialog.html">
+
+<test-fixture id="basic">
+ <template>
+ <gr-confirm-dialog></gr-confirm-dialog>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-confirm-dialog tests', function() {
+ var element;
+
+ setup(function() {
+ element = fixture('basic');
+ });
+
+ test('events', function(done) {
+ var numEvents = 0;
+ function handler() { if (++numEvents == 2) { done(); } }
+
+ element.addEventListener('confirm', handler);
+ element.addEventListener('cancel', handler);
+
+ MockInteractions.tap(element.$$('.confirm'));
+ MockInteractions.tap(element.$$('.cancel'));
+ });
+
+ });
+</script>
diff --git a/polygerrit-ui/app/test/gr-confirm-rebase-dialog-test.html b/polygerrit-ui/app/test/gr-confirm-rebase-dialog-test.html
new file mode 100644
index 0000000..7db82e9
--- /dev/null
+++ b/polygerrit-ui/app/test/gr-confirm-rebase-dialog-test.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-confirm-rebase-dialog</title>
+
+<script src="../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../bower_components/web-component-tester/browser.js"></script>
+
+<link rel="import" href="../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../elements/gr-confirm-rebase-dialog.html">
+
+<test-fixture id="basic">
+ <template>
+ <gr-confirm-rebase-dialog></gr-confirm-rebase-dialog>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-confirm-rebase-dialog tests', function() {
+ var element;
+
+ setup(function() {
+ element = fixture('basic');
+ });
+
+ test('controls', function() {
+ assert.isFalse(element.$.parentInput.hasAttribute('disabled'));
+ assert.isFalse(element.$.clearParent.checked);
+ element.base = 'something great';
+ MockInteractions.tap(element.$.clearParent);
+ assert.isTrue(element.$.parentInput.hasAttribute('disabled'));
+ assert.isTrue(element.$.clearParent.checked);
+ assert.equal(element.base, '');
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index 9c2099d..bf700c1 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -33,6 +33,8 @@
'gr-change-list-test.html',
'gr-change-star-test.html',
'gr-change-view-test.html',
+ 'gr-confirm-dialog-test.html',
+ 'gr-confirm-rebase-dialog-test.html',
'gr-date-formatter-test.html',
'gr-diff-comment-test.html',
'gr-diff-comment-thread-test.html',