add option to confirm overwrite on save (#9659)

* add option to confirm overwrite on save

* Make save options instead of using a boolean to increase readability.

* clean up comment

* Address code comments

- implicit returns from arrow function
- throw an error not an object

clean up implicit return confusion

use promise.reject instead of throw.
This commit is contained in:
Stacey Gammon 2016-12-30 12:51:27 -05:00 committed by GitHub
parent 024c81697e
commit e75e2a2100
3 changed files with 141 additions and 5 deletions

View file

@ -158,7 +158,7 @@ uiModules.get('apps/management')
return service.get().then(function (obj) {
obj.id = doc._id;
return obj.applyESResp(doc).then(function () {
return obj.save();
return obj.save({ confirmOverwrite : true });
});
});
})

View file

@ -22,6 +22,7 @@ describe('Saved Object', function () {
let esAdminStub;
let esDataStub;
let DocSource;
let window;
/**
* Some default es stubbing to avoid timeouts and allow a default type of 'dashboard'.
@ -86,19 +87,112 @@ describe('Saved Object', function () {
return savedObject.init();
}
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (es, esAdmin, Private) {
beforeEach(ngMock.module('kibana',
// The default implementation of safeConfirm uses $timeout which will cause
// the test environment to hang.
function ($provide) {
const overrideSafeConfirm = message => window.confirm(message) ? Promise.resolve() : Promise.reject();
$provide.decorator('safeConfirm', () => overrideSafeConfirm);
})
);
beforeEach(ngMock.inject(function (es, esAdmin, Private, $window) {
SavedObject = Private(SavedObjectFactory);
IndexPattern = Private(IndexPatternFactory);
esAdminStub = esAdmin;
esDataStub = es;
DocSource = Private(DocSourceProvider);
window = $window;
mockEsService();
stubMapper(Private);
}));
describe('save', function () {
describe('with confirmOverwrite', function () {
function stubConfirmOverwrite() {
window.confirm = sinon.stub().returns(true);
sinon.stub(esAdminStub, 'create').returns(BluebirdPromise.reject({ status : 409 }));
sinon.stub(esDataStub, 'create').returns(BluebirdPromise.reject({ status : 409 }));
}
describe('when true', function () {
it('requests confirmation and updates on yes response', function () {
stubESResponse(getMockedDocResponse('myId'));
return createInitializedSavedObject({ type: 'dashboard', id: 'myId' }).then(savedObject => {
stubConfirmOverwrite();
savedObject.lastSavedTitle = 'original title';
savedObject.title = 'new title';
return savedObject.save({ confirmOverwrite : true })
.then(() => {
expect(window.confirm.called).to.be(true);
expect(savedObject.id).to.be('myId');
expect(savedObject.isSaving).to.be(false);
expect(savedObject.lastSavedTitle).to.be('new title');
expect(savedObject.title).to.be('new title');
});
});
});
it('does not update on no response', function () {
stubESResponse(getMockedDocResponse('HI'));
return createInitializedSavedObject({ type: 'dashboard', id: 'HI' }).then(savedObject => {
window.confirm = sinon.stub().returns(false);
sinon.stub(esAdminStub, 'create').returns(BluebirdPromise.reject({ status : 409 }));
sinon.stub(esDataStub, 'create').returns(BluebirdPromise.reject({ status : 409 }));
savedObject.lastSavedTitle = 'original title';
savedObject.title = 'new title';
return savedObject.save({ confirmOverwrite : true })
.then(() => {
expect(savedObject.id).to.be('HI');
expect(savedObject.isSaving).to.be(false);
expect(savedObject.lastSavedTitle).to.be('original title');
expect(savedObject.title).to.be('new title');
});
});
});
it('handles doIndex failures', function () {
stubESResponse(getMockedDocResponse('myId'));
return createInitializedSavedObject({ type: 'dashboard', id: 'myId' }).then(savedObject => {
stubConfirmOverwrite();
esAdminStub.index.restore();
esDataStub.index.restore();
sinon.stub(esAdminStub, 'index').returns(BluebirdPromise.reject());
sinon.stub(esDataStub, 'index').returns(BluebirdPromise.reject());
return savedObject.save({ confirmOverwrite : true })
.then(() => {
expect(true).to.be(false); // Force failure, the save should not succeed.
})
.catch(() => {
expect(window.confirm.called).to.be(true);
});
});
});
});
it('when false does not request overwrite', function () {
const mockDocResponse = getMockedDocResponse('myId');
stubESResponse(mockDocResponse);
return createInitializedSavedObject({ type: 'dashboard', id: 'myId' }).then(savedObject => {
sinon.stub(DocSource.prototype, 'doCreate', function () {
return BluebirdPromise.reject({ 'origError' : { 'status' : 409 } });
});
stubConfirmOverwrite();
return savedObject.save({ confirmOverwrite : false })
.then(() => {
expect(window.confirm.called).to.be(false);
});
});
});
});
describe(' with copyOnSave', function () {
it('as true creates a copy on save success', function () {
const mockDocResponse = getMockedDocResponse('myId');

View file

@ -258,13 +258,53 @@ export default function SavedObjectFactory(esAdmin, kbnIndex, Promise, Private,
return esAdmin.indices.refresh({ index: kbnIndex });
}
/**
* An error message to be used when the user rejects a confirm overwrite.
* @type {string}
*/
const OVERWRITE_REJECTED = 'Overwrite confirmation was rejected';
/**
* Attempts to create the current object using the serialized source. If an object already
* exists, a warning message requests an overwrite confirmation.
* @param source - serialized version of this object (return value from this.serialize())
* What will be indexed into elasticsearch.
* @returns {Promise} - A promise that is resolved with the objects id if the object is
* successfully indexed. If the overwrite confirmation was rejected, an error is thrown with
* a confirmRejected = true parameter so that case can be handled differently than
* a create or index error.
* @resolved {String} - The id of the doc
*/
const createSource = (source) => {
return docSource.doCreate(source)
.catch((err) => {
// record exists, confirm overwriting
if (_.get(err, 'origError.status') === 409) {
const confirmMessage = `Are you sure you want to overwrite ${this.title}?`;
return safeConfirm(confirmMessage)
.then(() => docSource.doIndex(source))
.catch(() => Promise.reject(new Error(OVERWRITE_REJECTED)));
}
return Promise.reject(err);
});
};
/**
* @typedef {Object} SaveOptions
* @property {boolean} confirmOverwrite - If true, attempts to create the source so it
* can confirm an overwrite if a document with the id already exists.
*/
/**
* Saves this object.
*
* @param {SaveOptions} saveOptions?
* @return {Promise}
* @resolved {String} - The id of the doc
*/
this.save = () => {
this.save = (saveOptions = {}) => {
// Save the original id in case the save fails.
const originalId = this.id;
// Read https://github.com/elastic/kibana/issues/9056 and
@ -285,7 +325,8 @@ export default function SavedObjectFactory(esAdmin, kbnIndex, Promise, Private,
const source = this.serialize();
this.isSaving = true;
return docSource.doIndex(source)
const doSave = saveOptions.confirmOverwrite ? createSource(source) : docSource.doIndex(source);
return doSave
.then((id) => { this.id = id; })
.then(refreshIndex)
.then(() => {
@ -296,6 +337,7 @@ export default function SavedObjectFactory(esAdmin, kbnIndex, Promise, Private,
.catch((err) => {
this.isSaving = false;
this.id = originalId;
if (err && err.message === OVERWRITE_REJECTED) return;
return Promise.reject(err);
});
};