Skip to content

Commit

Permalink
add option to confirm overwrite on save
Browse files Browse the repository at this point in the history
  • Loading branch information
stacey-gammon committed Dec 30, 2016
1 parent 024c816 commit 5875c15
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 5 deletions.
Expand Up @@ -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(true);
});
});
})
Expand Down
101 changes: 99 additions & 2 deletions src/ui/public/courier/__tests__/saved_object.js
Expand Up @@ -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'.
Expand Down Expand Up @@ -86,19 +87,115 @@ 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) {
$provide.decorator('safeConfirm', () => {
return (message) => {
return window.confirm(message) ? Promise.resolve() : Promise.reject();
};
});
})
);
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(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(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(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(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');
Expand Down
35 changes: 33 additions & 2 deletions src/ui/public/courier/saved_object/saved_object.js
Expand Up @@ -258,13 +258,42 @@ export default function SavedObjectFactory(esAdmin, kbnIndex, Promise, Private,
return esAdmin.indices.refresh({ index: kbnIndex });
}

/**
* 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(
() => { return docSource.doIndex(source); },
() => { throw { confirmRejected : true }; }
);
}
return Promise.reject(err);
});
};

/**
* Saves this object.
*
* @param {bool} confirmOverwrite=false If true, attempts to create the source so it
* can confirm an overwrite if a document with the id already exists. Defaults to false.
* @return {Promise}
* @resolved {String} - The id of the doc
*/
this.save = () => {
this.save = (confirmOverwrite = false) => {
// Save the original id in case the save fails.
const originalId = this.id;
// Read https://github.com/elastic/kibana/issues/9056 and
Expand All @@ -285,7 +314,8 @@ export default function SavedObjectFactory(esAdmin, kbnIndex, Promise, Private,
const source = this.serialize();

this.isSaving = true;
return docSource.doIndex(source)
const doSave = confirmOverwrite ? createSource(source) : docSource.doIndex(source);
return doSave
.then((id) => { this.id = id; })
.then(refreshIndex)
.then(() => {
Expand All @@ -296,6 +326,7 @@ export default function SavedObjectFactory(esAdmin, kbnIndex, Promise, Private,
.catch((err) => {
this.isSaving = false;
this.id = originalId;
if (err && err.confirmRejected) return;
return Promise.reject(err);
});
};
Expand Down

0 comments on commit 5875c15

Please sign in to comment.