Cache edits made with PolyGerrit

Currently, it is quite easy to lose edits in process, as they are not
stored anywhere and there is no prompt before navigation away from
editing.

With this change, the editor view will cache edits to gr-storage at a
debounced rate, similar to gr-editable-content and gr-diff-comment.

These edits are keyed with the change number, patch number, and path of
the current file.

Bug: Issue 8481
Change-Id: I5de55e858225b6518ff5ff37f5b87cebdb84325a
This commit is contained in:
Kasper Nilsson
2018-04-03 16:14:47 -07:00
parent fb58901a47
commit aa4a07e893
3 changed files with 70 additions and 2 deletions

View File

@@ -27,6 +27,7 @@ limitations under the License.
<link rel="import" href="../../shared/gr-editable-label/gr-editable-label.html">
<link rel="import" href="../../shared/gr-fixed-panel/gr-fixed-panel.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-default-editor/gr-default-editor.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -120,6 +121,7 @@ limitations under the License.
</gr-endpoint-decorator>
</div>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
<gr-storage id="storage"></gr-storage>
</template>
<script src="gr-editor-view.js"></script>
</dom-module>

View File

@@ -17,10 +17,13 @@
(function() {
'use strict';
const RESTORED_MESSAGE = 'Content restored from a previous edit.';
const SAVING_MESSAGE = 'Saving changes...';
const SAVED_MESSAGE = 'All changes saved';
const SAVE_FAILED_MSG = 'Failed to save changes';
const STORAGE_DEBOUNCE_INTERVAL_MS = 100;
Polymer({
is: 'gr-editor-view',
@@ -87,6 +90,10 @@
this._getEditPrefs().then(prefs => { this._prefs = prefs; });
},
get storageKey() {
return `c${this._changeNum}_ps${this._patchNum}_${this._path}`;
},
_getLoggedIn() {
return this.$.restAPI.getLoggedIn();
},
@@ -143,9 +150,19 @@
},
_getFileData(changeNum, path, patchNum) {
const storedContent =
this.$.storage.getEditableContentItem(this.storageKey);
return this.$.restAPI.getFileContent(changeNum, path, patchNum)
.then(res => {
this._newContent = res.content || '';
if (storedContent && storedContent.message) {
this.dispatchEvent(new CustomEvent('show-alert',
{detail: {message: RESTORED_MESSAGE}, bubbles: true}));
this._newContent = storedContent.message;
} else {
this._newContent = res.content || '';
}
this._content = res.content || '';
// A non-ok response may result if the file does not yet exist.
@@ -162,6 +179,7 @@
_saveEdit() {
this._saving = true;
this._showAlert(SAVING_MESSAGE);
this.$.storage.eraseEditableContentItem(this.storageKey);
return this.$.restAPI.saveChangeEdit(this._changeNum, this._path,
this._newContent).then(res => {
this._saving = false;
@@ -191,7 +209,15 @@
},
_handleContentChange(e) {
if (e.detail.value) { this.set('_newContent', e.detail.value); }
this.debounce('store', () => {
const content = e.detail.value;
if (content) {
this.set('_newContent', e.detail.value);
this.$.storage.setEditableContentItem(this.storageKey, content);
} else {
this.$.storage.eraseEditableContentItem(this.storageKey);
}
}, STORAGE_DEBOUNCE_INTERVAL_MS);
},
_handleSaveShortcut(e) {

View File

@@ -118,14 +118,18 @@ suite('gr-editor-view tests', () => {
});
test('reacts to content-change event', () => {
const storeStub = sandbox.spy(element.$.storage, 'setEditableContentItem');
element._newContent = 'test';
element.$.editorEndpoint.dispatchEvent(new CustomEvent('content-change', {
bubbles: true,
detail: {value: 'new content value'},
}));
element.flushDebouncer('store');
flushAsynchronousOperations();
assert.equal(element._newContent, 'new content value');
assert.isTrue(storeStub.called);
assert.equal(storeStub.lastCall.args[1], 'new content value');
});
suite('edit file content', () => {
@@ -147,6 +151,8 @@ suite('gr-editor-view tests', () => {
test('file modification and save, !ok response', () => {
const saveSpy = sandbox.spy(element, '_saveEdit');
const eraseStub = sandbox.stub(element.$.storage,
'eraseEditableContentItem');
const alertStub = sandbox.stub(element, '_showAlert');
saveFileStub.returns(Promise.resolve({ok: false}));
element._newContent = newText;
@@ -163,6 +169,7 @@ suite('gr-editor-view tests', () => {
return saveSpy.lastCall.returnValue.then(() => {
assert.isTrue(saveFileStub.called);
assert.isTrue(eraseStub.called);
assert.isFalse(element._saving);
assert.equal(alertStub.lastCall.args[0], 'Failed to save changes');
assert.deepEqual(saveFileStub.lastCall.args,
@@ -219,6 +226,7 @@ suite('gr-editor-view tests', () => {
element._newContent = 'initial';
element._content = 'initial';
element._type = 'initial';
sandbox.stub(element.$.storage, 'getEditableContentItem').returns(null);
});
test('res.ok', () => {
@@ -343,5 +351,37 @@ suite('gr-editor-view tests', () => {
});
});
});
suite('gr-storage caching', () => {
test('local edit exists', () => {
sandbox.stub(element.$.storage, 'getEditableContentItem')
.returns({message: 'pending edit'});
sandbox.stub(element.$.restAPI, 'getFileContent')
.returns(Promise.resolve({
ok: true,
type: 'text/javascript',
content: 'old content',
}));
const alertStub = sandbox.stub();
element.addEventListener('show-alert', alertStub);
return element._getFileData(1, 'test', 1).then(() => {
flushAsynchronousOperations();
assert.isTrue(alertStub.called);
assert.equal(element._newContent, 'pending edit');
assert.equal(element._content, 'old content');
assert.equal(element._type, 'text/javascript');
});
});
test('storage key computation', () => {
element._changeNum = 1;
element._patchNum = 1;
element._path = 'test';
assert.equal(element.storageKey, 'c1_ps1_test');
});
});
});
</script>