Save top-level draft change comments in localstorage
Unsaved diff comment drafts are automatically backed-up in local-storage
if the user discards them on accident or navigates away without saving
them. This change extends the same functionality to change-level
comments and adds tests accordingly.
Bug: Issue 4258
Change-Id: Iecfdecfc10cd92f798f1f7306af994b6ec8f74db
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 ac1855b..e3f7eb7 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
@@ -509,7 +509,7 @@
_openReplyDialog: function(opt_section) {
this.$.replyOverlay.open().then(function() {
this.$.replyOverlay.setFocusStops(this.$.replyDialog.getFocusStops());
- this.$.replyDialog.focusOn(opt_section);
+ this.$.replyDialog.open(opt_section);
}.bind(this));
},
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
index e923d48..66a1bd0 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
@@ -22,6 +22,7 @@
<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+<link rel="import" href="../../shared/gr-storage/gr-storage.html">
<link rel="import" href="../gr-account-list/gr-account-list.html">
<dom-module id="gr-reply-dialog">
@@ -228,6 +229,7 @@
</div>
<gr-js-api-interface id="jsAPI"></gr-js-api-interface>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ <gr-storage id="storage"></gr-storage>
</template>
<script src="gr-reply-dialog.js"></script>
</dom-module>
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 b1f5d17..fa57770 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
@@ -14,6 +14,8 @@
(function() {
'use strict';
+ var STORAGE_DEBOUNCE_INTERVAL_MS = 400;
+
var FocusTarget = {
BODY: 'body',
REVIEWERS: 'reviewers',
@@ -46,6 +48,7 @@
draft: {
type: String,
value: '',
+ observer: '_draftChanged',
},
diffDrafts: Object,
labels: Object,
@@ -80,18 +83,15 @@
this.$.jsAPI.addElement(this.$.jsAPI.Element.REPLY_DIALOG, this);
},
- focus: function() {
- this.focusOn(FocusTarget.BODY);
+ open: function(opt_focusTarget) {
+ this._focusOn(opt_focusTarget);
+ if (!this.draft || !this.draft.length) {
+ this.draft = this._loadStoredDraft();
+ }
},
- focusOn: function(section) {
- if (section === FocusTarget.BODY) {
- var textarea = this.$.textarea;
- textarea.async(textarea.textarea.focus.bind(textarea.textarea));
- } else if (section === FocusTarget.REVIEWERS) {
- var reviewerEntry = this.$.reviewers.focusStart;
- reviewerEntry.async(reviewerEntry.focus);
- }
+ focus: function() {
+ this._focusOn(FocusTarget.BODY);
},
getFocusStops: function() {
@@ -160,6 +160,16 @@
}.bind(this));
},
+ _focusOn: function(section) {
+ if (section === FocusTarget.BODY) {
+ var textarea = this.$.textarea;
+ textarea.async(textarea.textarea.focus.bind(textarea.textarea));
+ } else if (section === FocusTarget.REVIEWERS) {
+ var reviewerEntry = this.$.reviewers.focusStart;
+ reviewerEntry.async(reviewerEntry.focus);
+ }
+ },
+
_computeShowLabels: function(patchNum, revisions) {
var num = parseInt(patchNum, 10);
for (var rev in revisions) {
@@ -279,12 +289,38 @@
_confirmPendingReviewer: function() {
this.$.reviewers.confirmGroup(this._reviewerPendingConfirmation.group);
- this.focusOn(FocusTarget.REVIEWERS);
+ this._focusOn(FocusTarget.REVIEWERS);
},
_cancelPendingReviewer: function() {
this._reviewerPendingConfirmation = null;
- this.focusOn(FocusTarget.REVIEWERS);
+ this._focusOn(FocusTarget.REVIEWERS);
+ },
+
+ _getStorageLocation: function() {
+ return {
+ changeNum: this.change._number,
+ patchNum: this.patchNum,
+ path: '@change',
+ };
+ },
+
+ _loadStoredDraft: function() {
+ var draft = this.$.storage.getDraftComment(this._getStorageLocation());
+ return draft ? draft.message : '';
+ },
+
+ _draftChanged: function(newDraft, oldDraft) {
+ this.debounce('store', function() {
+ if (!newDraft.length && oldDraft) {
+ // If the draft has been modified to be empty, then erase the storage
+ // entry.
+ this.$.storage.eraseDraftComment(this._getStorageLocation());
+ } else if (newDraft.length) {
+ this.$.storage.setDraftComment(this._getStorageLocation(),
+ this.draft);
+ }
+ }, STORAGE_DEBOUNCE_INTERVAL_MS);
},
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
index 0456be1..e65cc04 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
@@ -34,14 +34,27 @@
<script>
suite('gr-reply-dialog tests', function() {
var element;
+ var changeNum;
+ var patchNum;
+
+ var sandbox;
+ var getDraftCommentStub;
+ var setDraftCommentStub;
+ var eraseDraftCommentStub;
setup(function() {
+ sandbox = sinon.sandbox.create();
+
+ changeNum = 42;
+ patchNum = 1;
+
stub('gr-rest-api-interface', {
getAccount: function() { return Promise.resolve({}); },
});
+
element = fixture('basic');
- element.changeNum = 42;
- element.patchNum = 1;
+ element.change = { _number: changeNum };
+ element.patchNum = patchNum;
element.labels = {
Verified: {
values: {
@@ -75,10 +88,19 @@
]
};
+ getDraftCommentStub = sandbox.stub(element.$.storage, 'getDraftComment');
+ setDraftCommentStub = sandbox.stub(element.$.storage, 'setDraftComment');
+ eraseDraftCommentStub = sandbox.stub(element.$.storage,
+ 'eraseDraftComment');
+
// Allow the elements created by dom-repeat to be stamped.
flushAsynchronousOperations();
});
+ teardown(function() {
+ sandbox.restore();
+ });
+
test('cancel event', function(done) {
element.addEventListener('cancel', function() { done(); });
MockInteractions.tap(element.$$('.cancel'));
@@ -231,5 +253,42 @@
assert.equal(getActiveElement().id, 'input');
}).then(done);
});
+
+ test('_getStorageLocation', function() {
+ var actual = element._getStorageLocation();
+ assert.equal(actual.changeNum, changeNum);
+ assert.equal(actual.patchNum, patchNum);
+ assert.equal(actual.path, '@change');
+ });
+
+ test('gets draft from storage on open', function() {
+ var storedDraft = 'hello world';
+ getDraftCommentStub.returns({message: storedDraft});
+ element.open();
+ assert.isTrue(getDraftCommentStub.called);
+ assert.equal(element.draft, storedDraft);
+ });
+
+ test('blank if no stored draft', function() {
+ getDraftCommentStub.returns(null);
+ element.open();
+ assert.isTrue(getDraftCommentStub.called);
+ assert.equal(element.draft, '');
+ });
+
+ test('updates stored draft on edits', function() {
+ var firstEdit = 'hello';
+ var location = element._getStorageLocation();
+
+ element.draft = firstEdit;
+ element.flushDebouncer('store');
+
+ assert.isTrue(setDraftCommentStub.calledWith(location, firstEdit));
+
+ element.draft = '';
+ element.flushDebouncer('store');
+
+ assert.isTrue(eraseDraftCommentStub.calledWith(location));
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
index a2afb89..ff41a74 100644
--- a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
@@ -58,7 +58,7 @@
_getDraftKey: function(location) {
return ['draft', location.changeNum, location.patchNum, location.path,
- location.line].join(':');
+ location.line || ''].join(':');
},
_cleanupDrafts: function() {