You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by kx...@apache.org on 2014/06/07 23:04:35 UTC
[11/15] fauxton commit: updated refs/heads/import-master to 8cb432c
Fauxton: Implement bulk deletion for all-docs-listing
Introduce a collection which keeps track of documents that will
deleted using the CouchDB Bulk-update API.
The collection fires events, so the view is noticed.
Closes COUCHDB-2153
Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/c50fca5b
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/c50fca5b
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/c50fca5b
Branch: refs/heads/import-master
Commit: c50fca5baf5d65feed90cc56ac6e0be81ff99150
Parents: 4510053
Author: Robert Kowalski <ro...@kowalski.gd>
Authored: Fri May 2 21:39:21 2014 +0200
Committer: Robert Kowalski <ro...@kowalski.gd>
Committed: Sat May 31 22:57:25 2014 +0200
----------------------------------------------------------------------
app/addons/documents/resources.js | 90 +++++++++-
app/addons/documents/routes.js | 3 +-
.../documents/templates/all_docs_item.html | 4 +-
.../documents/templates/all_docs_list.html | 6 +-
app/addons/documents/tests/resourcesSpec.js | 83 ++++++++-
app/addons/documents/views.js | 178 ++++++++++++-------
6 files changed, 290 insertions(+), 74 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/c50fca5b/app/addons/documents/resources.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/resources.js b/app/addons/documents/resources.js
index 21ee55f..21cdfdd 100644
--- a/app/addons/documents/resources.js
+++ b/app/addons/documents/resources.js
@@ -44,7 +44,7 @@ function(app, FauxtonAPI, PagingCollection) {
};
})();
-
+
Documents.Doc = FauxtonAPI.Model.extend({
idAttribute: "_id",
documentation: function(){
@@ -302,6 +302,94 @@ function(app, FauxtonAPI, PagingCollection) {
});
+ Documents.BulkDeleteDoc = FauxtonAPI.Model.extend({
+ idAttribute: "_id"
+ });
+
+ Documents.BulkDeleteDocCollection = FauxtonAPI.Collection.extend({
+
+ model: Documents.BulkDeleteDoc,
+
+ sync: function () {
+
+ },
+
+ initialize: function (models, options) {
+ this.databaseId = options.databaseId;
+ },
+
+ bulkDelete: function () {
+ var payload = this.createPayload(this.toJSON()),
+ that = this;
+
+ $.ajax({
+ type: 'POST',
+ url: app.host + '/' + this.databaseId + '/_bulk_docs',
+ contentType: 'application/json',
+ dataType: 'json',
+ data: JSON.stringify(payload),
+ })
+ .then(function (res) {
+ that.handleResponse(res);
+ })
+ .fail(function () {
+ var ids = _.reduce(that.toArray(), function (acc, doc) {
+ acc.push(doc.id);
+ return acc;
+ }, []);
+ that.trigger('error', ids);
+ });
+ },
+
+ handleResponse: function (res) {
+ var ids = _.reduce(res, function (ids, doc) {
+ if (doc.error) {
+ ids.errorIds.push(doc.id);
+ }
+
+ if (doc.ok === true) {
+ ids.successIds.push(doc.id);
+ }
+
+ return ids;
+ }, {errorIds: [], successIds: []});
+
+ this.removeDocuments(ids.successIds);
+
+ if (ids.errorIds.length) {
+ this.trigger('error', ids.errorIds);
+ }
+
+ this.trigger('updated');
+ },
+
+ removeDocuments: function (ids) {
+ var reloadDesignDocs = false;
+ _.each(ids, function (id) {
+ if (/_design/.test(id)) {
+ reloadDesignDocs = true;
+ }
+
+ this.remove(this.get(id));
+ }, this);
+
+ if (reloadDesignDocs) {
+ FauxtonAPI.triggerRouteEvent('reloadDesignDocs');
+ }
+
+ this.trigger('removed', ids);
+ },
+
+ createPayload: function (documents) {
+ var documentList = documents;
+
+ return {
+ docs: documentList
+ };
+ }
+ });
+
+
Documents.AllDocs = PagingCollection.extend({
model: Documents.Doc,
documentation: function(){
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/c50fca5b/app/addons/documents/routes.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/routes.js b/app/addons/documents/routes.js
index a24a3bd..6d67dae 100644
--- a/app/addons/documents/routes.js
+++ b/app/addons/documents/routes.js
@@ -224,7 +224,8 @@ function(app, FauxtonAPI, Documents, Databases) {
database: this.data.database,
collection: this.data.database.allDocs,
docParams: docParams,
- params: urlParams
+ params: urlParams,
+ bulkDeleteDocsCollection: new Documents.BulkDeleteDocCollection([], {databaseId: this.data.database.get('id')})
}));
this.crumbs = [
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/c50fca5b/app/addons/documents/templates/all_docs_item.html
----------------------------------------------------------------------
diff --git a/app/addons/documents/templates/all_docs_item.html b/app/addons/documents/templates/all_docs_item.html
index bfedaaa..a8ef20f 100644
--- a/app/addons/documents/templates/all_docs_item.html
+++ b/app/addons/documents/templates/all_docs_item.html
@@ -12,13 +12,13 @@ License for the specific language governing permissions and limitations under
the License.
-->
-<td class="select"><input type="checkbox" class="row-select"></td>
+<td class="select"><input <%- checked ? 'checked="checked"' : '' %> type="checkbox" class="js-row-select"></td>
<td>
<div>
<pre class="prettyprint"><%- doc.prettyJSON() %></pre>
<% if (doc.isEditable()) { %>
<div class="btn-group">
- <a href="#<%= doc.url('web-index') %>" class="btn btn-small edits">Edit <%= doc.docType() %></a>
+ <a href="#<%= doc.url('web-index') %>" class="btn btn-small edits">Edit <%- doc.docType() %></a>
<button href="#" class="btn btn-small btn-danger delete" title="Delete this document."><i class="icon icon-trash"></i></button>
</div>
<% } %>
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/c50fca5b/app/addons/documents/templates/all_docs_list.html
----------------------------------------------------------------------
diff --git a/app/addons/documents/templates/all_docs_list.html b/app/addons/documents/templates/all_docs_list.html
index a521ff9..a643427 100644
--- a/app/addons/documents/templates/all_docs_list.html
+++ b/app/addons/documents/templates/all_docs_list.html
@@ -17,7 +17,7 @@ the License.
<div class="row">
<div class="btn-toolbar span6">
<button type="button" class="btn btn-small all" data-toggle="button">✓ All</button>
- <button class="btn btn-small disabled bulk-delete"><i class="icon-trash"></i></button>
+ <button class="btn btn-small disabled js-bulk-delete"><i class="icon-trash"></i></button>
<% if (expandDocs) { %>
<button id="collapse" class="btn btn-small"><i class="icon-minus"></i> Collapse</button>
<% } else { %>
@@ -32,8 +32,8 @@ the License.
<table class="all-docs table table-striped table-condensed">
<tbody></tbody>
</table>
-
- <% if (endOfResults) { %>
+
+ <% if (endOfResults) { %>
<div class="text-center well">
<p class="muted">
End of results - <a id="js-end-results" href="#query" data-bypass="true" data-toggle="tab">edit query</a>
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/c50fca5b/app/addons/documents/tests/resourcesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/tests/resourcesSpec.js b/app/addons/documents/tests/resourcesSpec.js
index e120582..1159360 100644
--- a/app/addons/documents/tests/resourcesSpec.js
+++ b/app/addons/documents/tests/resourcesSpec.js
@@ -10,8 +10,8 @@
// License for the specific language governing permissions and limitations under
// the License.
define([
- 'addons/documents/resources',
- 'testUtils'
+ 'addons/documents/resources',
+ 'testUtils'
], function (Models, testUtils) {
var assert = testUtils.assert;
@@ -50,7 +50,6 @@ define([
});
});
-
});
describe('QueryParams', function() {
@@ -148,5 +147,81 @@ define([
});
});
});
-});
+ describe('Bulk Delete', function () {
+ var databaseId = 'ente',
+ collection,
+ values;
+
+ values = [{
+ _id: '1',
+ _rev: '1234561',
+ _deleted: true
+ },
+ {
+ _id: '2',
+ _rev: '1234562',
+ _deleted: true
+ },
+ {
+ _id: '3',
+ _rev: '1234563',
+ _deleted: true
+ }];
+
+ beforeEach(function () {
+ collection = new Models.BulkDeleteDocCollection(values, {
+ databaseId: databaseId
+ });
+ });
+
+ it("contains the models", function () {
+ collection = new Models.BulkDeleteDocCollection(values, {
+ databaseId: databaseId
+ });
+
+ assert.equal(collection.length, 3);
+ });
+
+ it("clears the memory if no errors happened", function () {
+ collection.handleResponse([
+ {"ok":true,"id":"1","rev":"10-72cd2edbcc0d197ce96188a229a7af01"},
+ {"ok":true,"id":"2","rev":"6-da537822b9672a4b2f42adb1be04a5b1"}
+ ]);
+
+ assert.equal(collection.length, 1);
+ });
+
+ it("triggers a removed event with all ids", function () {
+ collection.listenToOnce(collection, 'removed', function (ids) {
+ assert.deepEqual(ids, ['Deferred', 'DeskSet']);
+ });
+
+ collection.handleResponse([
+ {"ok":true,"id":"Deferred","rev":"10-72cd2edbcc0d197ce96188a229a7af01"},
+ {"ok":true,"id":"DeskSet","rev":"6-da537822b9672a4b2f42adb1be04a5b1"}
+ ]);
+ });
+
+ it("triggers a error event with all errored ids", function () {
+ collection.listenToOnce(collection, 'error', function (ids) {
+ assert.deepEqual(ids, ['Deferred']);
+ });
+ collection.handleResponse([
+ {"error":"confclict","id":"Deferred","rev":"10-72cd2edbcc0d197ce96188a229a7af01"},
+ {"ok":true,"id":"DeskSet","rev":"6-da537822b9672a4b2f42adb1be04a5b1"}
+ ]);
+ });
+
+ it("removes successfull deleted from the collection but keeps one with errors", function () {
+ collection.handleResponse([
+ {"error":"confclict","id":"1","rev":"10-72cd2edbcc0d197ce96188a229a7af01"},
+ {"ok":true,"id":"2","rev":"6-da537822b9672a4b2f42adb1be04a5b1"},
+ {"error":"conflict","id":"3","rev":"6-da537822b9672a4b2f42adb1be04a5b1"}
+ ]);
+ assert.ok(collection.get('1'));
+ assert.ok(collection.get('3'));
+ assert.notOk(collection.get('2'));
+ });
+ });
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/c50fca5b/app/addons/documents/views.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/views.js b/app/addons/documents/views.js
index 87bc7ae..97f82b4 100644
--- a/app/addons/documents/views.js
+++ b/app/addons/documents/views.js
@@ -33,6 +33,15 @@ define([
function(app, FauxtonAPI, Components, Documents, Databases, pouchdb,
resizeColumns, beautify, prettify, ZeroClipboard) {
+
+ function showError (msg) {
+ FauxtonAPI.addNotification({
+ msg: msg,
+ type: 'error',
+ clear: true
+ });
+ }
+
var Views = {};
Views.SearchBox = FauxtonAPI.View.extend({
@@ -243,6 +252,10 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb,
tagName: "tr",
className: "all-docs-item",
+ initialize: function (options) {
+ this.checked = options.checked;
+ },
+
events: {
"click button.delete": "destroy",
"dblclick pre.prettyprint": "edit"
@@ -256,7 +269,8 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb,
serialize: function() {
return {
- doc: this.model
+ doc: this.model,
+ checked: this.checked
};
},
@@ -279,7 +293,7 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb,
this.model.destroy().then(function(resp) {
FauxtonAPI.addNotification({
- msg: "Succesfully destroyed your doc",
+ msg: "Succesfully deleted your doc",
clear: true
});
that.$el.fadeOut(function () {
@@ -292,7 +306,7 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb,
}
}, function(resp) {
FauxtonAPI.addNotification({
- msg: "Failed to destroy your doc!",
+ msg: "Failed to deleted your doc!",
type: "error",
clear: true
});
@@ -499,29 +513,16 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb,
template: "addons/documents/templates/all_docs_list",
events: {
"click button.all": "selectAll",
- "click button.bulk-delete": "bulkDelete",
+ "click button.js-bulk-delete": "bulkDelete",
"click #collapse": "collapse",
- "change .row-select":"toggleTrash",
+ "click .all-docs-item": "toggleDocument",
"click #js-end-results": "scrollToQuery"
},
- toggleTrash: function () {
- if (this.$('.row-select:checked').length > 0) {
- this.$('.bulk-delete').removeClass('disabled');
- } else {
- this.$('.bulk-delete').addClass('disabled');
- }
- },
-
- scrollToQuery: function () {
- $('#dashboard-content').animate({ scrollTop: 0 }, 'slow');
- },
-
- initialize: function(options){
+ initialize: function (options) {
this.nestedView = options.nestedView || Views.Document;
this.rows = {};
- this.viewList = !! options.viewList;
- this.database = options.database;
+ this.viewList = !!options.viewList;
if (options.ddocInfo) {
this.designDocs = options.ddocInfo.designDocs;
@@ -532,6 +533,74 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb,
this.params = options.params || {};
this.expandDocs = true;
this.perPageDefault = this.docParams.limit || 20;
+
+ // some doclists don't have an option to delete
+ if (!this.viewList) {
+ this.bulkDeleteDocsCollection = options.bulkDeleteDocsCollection;
+ }
+ },
+
+ removeDocuments: function (ids) {
+ _.each(ids, function (id) {
+ this.removeDocument(id);
+ }, this);
+
+ this.pagination.updatePerPage(parseInt(this.$('#select-per-page :selected').val(), 10));
+ FauxtonAPI.triggerRouteEvent('perPageChange', this.pagination.documentsLeftToFetch());
+ },
+
+ removeDocument: function (id) {
+ var that = this;
+
+ if (!this.rows[id]) {
+ return;
+ }
+
+ this.rows[id].$el.fadeOut('slow', function () {
+ that.rows[id].remove();
+ });
+ },
+
+ showError: function (ids) {
+ if (ids) {
+ showError('Failed to delete: ' + ids.join(', '));
+ return;
+ }
+
+ showError('Failed to delete your doc!');
+ },
+
+ toggleDocument: function (event) {
+ var $row = this.$(event.target).closest('tr'),
+ docId = $row.attr('data-id'),
+ db = this.database.get('id'),
+ rev = this.collection.get(docId).get('_rev'),
+ data = {_id: docId, _rev: rev, _deleted: true};
+
+ if (!$row.hasClass('js-to-delete')) {
+ this.bulkDeleteDocsCollection.add(data);
+ } else {
+ this.bulkDeleteDocsCollection.remove(this.bulkDeleteDocsCollection.get(docId));
+ }
+
+ $row.find('.js-row-select').prop('checked', !$row.hasClass('js-to-delete'));
+ $row.toggleClass('js-to-delete');
+
+ this.toggleTrash();
+ },
+
+ toggleTrash: function () {
+ var $bulkdDeleteButton = this.$('.js-bulk-delete');
+
+ if (this.bulkDeleteDocsCollection.length > 0) {
+ $bulkdDeleteButton.removeClass('disabled');
+ } else {
+ $bulkdDeleteButton.addClass('disabled');
+ }
+ },
+
+ scrollToQuery: function () {
+ $('#dashboard-content').animate({ scrollTop: 0 }, 'slow');
},
establish: function() {
@@ -551,7 +620,6 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb,
//now redirect back to alldocs
FauxtonAPI.navigate(model.database.url("index") + "?limit=100");
- console.log("ERROR: ", arguments);
}
});
},
@@ -580,48 +648,17 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb,
this.render();
},
- /*
- * TODO: this should be reconsidered
- * This currently performs delete operations on the model level,
- * when we could be using bulk docs with _deleted = true. Using
- * individual models is cleaner from a backbone standpoint, but
- * not from the couchdb api.
- * Also, the delete method is naive and leaves the body intact,
- * when we should switch the doc to only having id/rev/deleted.
- */
bulkDelete: function() {
- var that = this;
- // yuck, data binding ftw?
- var eles = this.$el.find("input.row-select:checked")
- .parents("tr.all-docs-item")
- .map(function(e) { return $(this).attr("data-id"); })
- .get();
+ var that = this,
+ documentsLength = this.bulkDeleteDocsCollection.length,
+ msg;
- if (eles.length === 0 || !window.confirm("Are you sure you want to delete these " + eles.length + " docs?")) {
+ msg = "Are you sure you want to delete these " + documentsLength + " docs?";
+ if (documentsLength === 0 || !window.confirm(msg)) {
return false;
}
- _.each(eles, function(ele) {
- var model = this.collection.get(ele);
-
- model.destroy().then(function(resp) {
- that.rows[ele].$el.fadeOut(function () {
- $(this).remove();
- });
-
- model.collection.remove(model.id);
- if (!!model.id.match('_design')) {
- FauxtonAPI.triggerRouteEvent('reloadDesignDocs');
- }
- that.$('.bulk-delete').addClass('disabled');
- }, function(resp) {
- FauxtonAPI.addNotification({
- msg: "Failed to destroy your doc!",
- type: "error",
- clear: true
- });
- });
- }, this);
+ this.bulkDeleteDocsCollection.bulkDelete();
},
addPagination: function () {
@@ -640,6 +677,7 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb,
},
beforeRender: function() {
+ var docs;
if (!this.pagination) {
this.addPagination();
@@ -658,11 +696,16 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb,
this.setView('#item-numbers', this.allDocsNumber);
- var docs = this.expandDocs ? this.collection : this.collection.simple();
+ docs = this.expandDocs ? this.collection : this.collection.simple();
docs.each(function(doc) {
+ var isChecked;
+ if (this.bulkDeleteDocsCollection) {
+ isChecked = this.bulkDeleteDocsCollection.get(doc.id);
+ }
this.rows[doc.id] = this.insertView("table.all-docs tbody", new this.nestedView({
- model: doc
+ model: doc,
+ checked: isChecked
}));
}, this);
},
@@ -683,8 +726,17 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb,
}
},
- afterRender: function(){
+ afterRender: function () {
prettyPrint();
+
+ if (this.bulkDeleteDocsCollection) {
+ this.stopListening(this.bulkDeleteDocsCollection);
+ this.listenTo(this.bulkDeleteDocsCollection, 'error', this.showError);
+ this.listenTo(this.bulkDeleteDocsCollection, 'removed', this.removeDocuments);
+ this.listenTo(this.bulkDeleteDocsCollection, 'updated', this.toggleTrash);
+ }
+
+ this.toggleTrash();
},
perPage: function () {
@@ -731,13 +783,13 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb,
this.model.destroy().then(function(resp) {
FauxtonAPI.addNotification({
- msg: "Succesfully destroyed your doc",
+ msg: "Succesfully deleted your doc",
clear: true
});
FauxtonAPI.navigate(database.url("index"));
}, function(resp) {
FauxtonAPI.addNotification({
- msg: "Failed to destroy your doc!",
+ msg: "Failed to delete your doc!",
type: "error",
clear: true
});