don't allow deletion of docs without rev
if we have no rev, bulk deletion is not possible, so we should
not try to offer it.
Signed-off-by: Michelle Phung <michellep@apache.org>
diff --git a/app/addons/documents/index-results/index-results.components.react.jsx b/app/addons/documents/index-results/index-results.components.react.jsx
index bbcd196..92cdfdc 100644
--- a/app/addons/documents/index-results/index-results.components.react.jsx
+++ b/app/addons/documents/index-results/index-results.components.react.jsx
@@ -86,8 +86,8 @@
},
maybeGetCheckboxCell: function (i) {
- if (!this.props.docId) {
- return null;
+ if (!this.props.data.isDeletable) {
+ return <td className="tableview-checkbox-cell" key={"tableview-checkbox-cell-" + i}></td>;
}
return (
@@ -123,6 +123,7 @@
return (
<TableRow
key={"tableview-row-component-" + i}
+ isListDeletable={this.props.isListDeletable}
index={i}
data={el}
docId={el.id}
@@ -258,13 +259,13 @@
var mainView = isTableView ? this.getTableStyleView(loadLines) : this.getDocumentStyleView(loadLines);
return (
<div className="document-result-screen">
- <BulkActionComponent
+ {this.props.isListDeletable ? <BulkActionComponent
removeItem={this.props.removeItem}
isChecked={this.props.allDocumentsSelected}
hasSelectedItem={this.props.hasSelectedItem}
selectAll={this.selectAllDocs}
toggleSelect={this.toggleSelectAll}
- title="Select all docs that can be..." />
+ title="Select all docs that can be..." /> : null}
{mainView}
</div>
);
@@ -347,7 +348,7 @@
canSelectAll={this.state.canSelectAll}
isSelected={this.isSelected}
isEditable={this.state.isEditable}
- isListDeletable={this.state.results.hasEditableAndDeletableDoc}
+ isListDeletable={this.state.results.hasDeletableDoc}
docChecked={this.docChecked}
isLoading={this.state.isLoading}
results={this.state.results}
diff --git a/app/addons/documents/index-results/stores.js b/app/addons/documents/index-results/stores.js
index 18d2f06..5a4766e 100644
--- a/app/addons/documents/index-results/stores.js
+++ b/app/addons/documents/index-results/stores.js
@@ -104,7 +104,7 @@
return false;
}
- return doc.isDeletable();
+ return doc.isBulkDeletable();
},
getCollection: function () {
@@ -179,29 +179,30 @@
},
+ filterOutGeneratedMangoDocs: function (doc) {
+ if (doc.get && typeof doc.get === 'function') {
+ return doc.get('language') !== 'query';
+ }
+
+ return doc.language !== 'query';
+ },
+
getResults: function () {
- var hasEditableAndDeletableDoc;
+ var hasDeletableDoc;
var res;
var collection;
-
- function filterOutGeneratedMangoDocs (doc) {
- if (doc.get && typeof doc.get === 'function') {
- return doc.get('language') !== 'query';
- }
-
- return doc.language !== 'query';
- }
+ var filteredCollection = this._collection.filter(this.filterOutGeneratedMangoDocs);
// Table sytle view
if (this.getIsTableView()) {
- collection = this._collection.toJSON().filter(filterOutGeneratedMangoDocs);
+ collection = this._collection.toJSON().filter(this.filterOutGeneratedMangoDocs);
return this.getTableViewData(collection);
}
// JSON style views
res = this._collection
- .filter(filterOutGeneratedMangoDocs)
+ .filter(this.filterOutGeneratedMangoDocs)
.map(function (doc, i) {
if (doc.get('def') || this.isGhostDoc(doc)) {
return this.getMangoDoc(doc, i);
@@ -217,10 +218,10 @@
};
}, this);
- hasEditableAndDeletableDoc = this.getHasEditableAndDeletableDoc(res);
+ hasDeletableDoc = this.getHasDeletableDoc(res);
return {
- hasEditableAndDeletableDoc: hasEditableAndDeletableDoc,
+ hasDeletableDoc: hasDeletableDoc,
results: res
};
},
@@ -259,7 +260,7 @@
return data;
},
- getTableViewData: function (data, hasEditableAndDeletableDoc) {
+ getTableViewData: function (data) {
var res;
var schema;
var database;
@@ -268,42 +269,36 @@
schema = this.getPseudoSchema(data);
database = this.getDatabase().safeID();
- res = data.map(function (doc) {
- var safeId = app.utils.getSafeIdForDoc(doc._id);
- var url;
+ res = this._collection
+ .filter(this.filterOutGeneratedMangoDocs)
+ .map(function (doc) {
- if (safeId) {
- url = FauxtonAPI.urls('document', 'app', database, safeId);
- }
-
- return {
- content: doc,
- id: safeId,
- header: '',
- keylabel: '',
- url: url,
- isDeletable: !!safeId,
- isEditable: !!safeId
- };
- });
-
- hasEditableAndDeletableDoc = this.getHasEditableAndDeletableDoc(res);
+ return {
+ content: doc.toJSON(),
+ id: this.getDocId(doc),
+ header: '',
+ keylabel: '',
+ url: this.getDocId(doc) ? doc.url('app') : null,
+ isDeletable: this.isDeletable(doc),
+ isEditable: this.isEditable(doc)
+ };
+ }, this);
return {
- hasEditableAndDeletableDoc: hasEditableAndDeletableDoc,
+ hasDeletableDoc: this.getHasDeletableDoc(res),
schema: schema,
results: res
};
},
- getHasEditableAndDeletableDoc: function (data) {
+ getHasDeletableDoc: function (data) {
var found = false;
var length = data.length;
var i;
-
// use a for loop here as we can end it once we found the first id
for (i = 0; i < length; i++) {
- if (data[i].id) {
+
+ if (data[i].isDeletable) {
found = true;
break;
}
@@ -343,6 +338,9 @@
selectAllDocuments: function () {
this.clearSelectedItems();
this._collection.each(function (doc) {
+ if (!doc.isBulkDeletable()) {
+ return;
+ }
this.selectDoc(doc.id);
}, this);
},
@@ -356,11 +354,17 @@
},
areAllDocumentsSelected: function () {
+ var filtered;
+
if (this._collection.length === 0) {
return false;
}
- return Object.keys(this._selectedItems).length === this._collection.length;
+ filtered = this._collection.filter(function (doc) {
+ return doc.isBulkDeletable();
+ });
+
+ return Object.keys(this._selectedItems).length === filtered.length;
},
getSelectedItemsLength: function () {
diff --git a/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx b/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx
index ee4811c..0589c7a 100644
--- a/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx
+++ b/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx
@@ -16,9 +16,11 @@
'addons/documents/index-results/stores',
'addons/documents/resources',
'addons/databases/resources',
+
+ 'addons/documents/tests/document-test-helper',
'testUtils',
"react"
-], function (FauxtonAPI, Views, IndexResultsActions, Stores, Documents, Databases, utils, React) {
+], function (FauxtonAPI, Views, IndexResultsActions, Stores, Documents, Databases, documentTestHelper, utils, React) {
FauxtonAPI.router = new FauxtonAPI.Router([]);
var assert = utils.assert;
@@ -82,17 +84,13 @@
store.reset();
});
- function createDocColumn (docs) {
- docs = docs.map(function (doc) {
- return Documents.Doc.prototype.parse(doc);
- });
+ var createDocColumn = documentTestHelper.createDocColumn;
+ var createMangoIndexDocColumn = documentTestHelper.createMangoIndexDocColumn;
- return new Documents.AllDocs(docs, opts);
- }
- it('does not render checkboxes for elements with no id in a table', function () {
+ it('does not render checkboxes for elements with just the special index (Mango Index List)', function () {
IndexResultsActions.sendMessageNewResultList({
- collection: createDocColumn([{foo: 'testId1'}, {bar: 'testId1'}])
+ collection: createMangoIndexDocColumn([{foo: 'testId1', type: 'special'}])
});
store.enableTableView();
@@ -109,9 +107,82 @@
assert.ok($el.find('.tableview-header-el-checkbox').length === 0);
});
- it('renders checkboxes for elements with id in a table', function () {
+ it('renders checkboxes for elements with more than just the the special index (Mango Index List)', function () {
IndexResultsActions.sendMessageNewResultList({
- collection: createDocColumn([{id: '1', foo: 'testId1'}, {bar: 'testId1'}])
+ collection: createMangoIndexDocColumn([{
+ ddoc: null,
+ name: 'biene',
+ type: 'json',
+ def: {fields: [{_id: 'desc'}]}
+ },
+ {
+ ddoc: null,
+ name: 'biene',
+ type: 'special',
+ def: {fields: [{_id: 'desc'}]}
+ }])
+ });
+
+ store.enableTableView();
+
+ IndexResultsActions.resultsListReset();
+
+ instance = TestUtils.renderIntoDocument(
+ <Views.List />,
+ container
+ );
+
+ var $el = $(instance.getDOMNode());
+
+ assert.ok($el.find('.tableview-header-el-checkbox').length > 0);
+ });
+
+ it('does not render checkboxes for elements with no id in a table (usual docs)', function () {
+ IndexResultsActions.sendMessageNewResultList({
+ collection: createDocColumn([{
+ ddoc: null,
+ name: 'biene',
+ type: 'special',
+ def: {fields: [{_id: 'desc'}]}
+ }])
+ });
+
+ store.enableTableView();
+
+ IndexResultsActions.resultsListReset();
+
+ instance = TestUtils.renderIntoDocument(
+ <Views.List />,
+ container
+ );
+
+ var $el = $(instance.getDOMNode());
+
+ assert.ok($el.find('.tableview-header-el-checkbox').length === 0);
+ });
+
+ it('does not render checkboxes for elements with no rev in a table (usual docs)', function () {
+ IndexResultsActions.sendMessageNewResultList({
+ collection: createDocColumn([{id: '1', foo: 'testId1'}, {id: '1', bar: 'testId1'}])
+ });
+
+ store.enableTableView();
+
+ IndexResultsActions.resultsListReset();
+
+ instance = TestUtils.renderIntoDocument(
+ <Views.List />,
+ container
+ );
+
+ var $el = $(instance.getDOMNode());
+
+ assert.ok($el.find('.tableview-header-el-checkbox').length === 0);
+ });
+
+ it('renders checkboxes for elements with an id and rev in a table (usual docs)', function () {
+ IndexResultsActions.sendMessageNewResultList({
+ collection: createDocColumn([{id: '1', foo: 'testId1', rev: 'foo'}, {bar: 'testId1', rev: 'foo'}])
});
store.enableTableView();
@@ -128,9 +199,9 @@
assert.ok($el.find('.tableview-checkbox-cell').length > 0);
});
- it('renders checkboxes for elements with an id in a json view', function () {
+ it('renders checkboxes for elements with an id and rev in a json view (usual docs)', function () {
IndexResultsActions.sendMessageNewResultList({
- collection: createDocColumn([{id: '1', foo: 'testId1'}, {bar: 'testId1'}])
+ collection: createDocColumn([{id: '1', emma: 'testId1', rev: 'foo'}, {bar: 'testId1'}])
});
IndexResultsActions.resultsListReset();
@@ -143,13 +214,12 @@
);
var $el = $(instance.getDOMNode());
-
assert.ok($el.find('.js-row-select').length > 0);
});
- it('does not render checkboxes for elements with no id in a json view', function () {
+ it('does not render checkboxes for elements with that are not deletable in a json view (usual docs)', function () {
IndexResultsActions.sendMessageNewResultList({
- collection: createDocColumn([{foo: 'testId1'}, {bar: 'testId1'}])
+ collection: createDocColumn([{foo: 'testId1', rev: 'foo'}, {bar: 'testId1'}])
});
IndexResultsActions.resultsListReset();
diff --git a/app/addons/documents/index-results/tests/index-results.storesSpec.js b/app/addons/documents/index-results/tests/index-results.storesSpec.js
index 304cf87..dec5d50 100644
--- a/app/addons/documents/index-results/tests/index-results.storesSpec.js
+++ b/app/addons/documents/index-results/tests/index-results.storesSpec.js
@@ -15,13 +15,18 @@
'addons/documents/index-results/stores',
'addons/documents/index-results/actiontypes',
'addons/documents/shared-resources',
+ 'addons/documents/tests/document-test-helper',
+
'testUtils'
-], function (FauxtonAPI, Stores, ActionTypes, Documents, testUtils) {
+], function (FauxtonAPI, Stores, ActionTypes, Documents, documentTestHelper, testUtils) {
var assert = testUtils.assert;
var dispatchToken;
var store;
var opts;
+ var createDocColumn = documentTestHelper.createDocColumn;
+ var createMangoIndexDocColumn = documentTestHelper.createMangoIndexDocColumn;
+
describe('Index Results Store', function () {
beforeEach(function () {
store = new Stores.IndexResultsStore();
@@ -120,20 +125,21 @@
assert.deepEqual(doclist, res);
});
- it('finds out if we have at least one editable/deleteable doc which needs an id', function () {
+ it('finds out if we have at least one editable/deleteable doc', function () {
var doclist = [
- {id: 'testId2', foo: 'one'},
+ {id: 'testId2', foo: 'one', isDeletable: true},
{id: 'testId3', foo: 'two'}
];
- assert.ok(store.getHasEditableAndDeletableDoc(doclist));
+ assert.ok(store.getHasDeletableDoc(doclist));
doclist = [
{foo: 'one'},
{foo: 'two'}
];
- assert.notOk(store.getHasEditableAndDeletableDoc(doclist));
+ assert.notOk(store.getHasDeletableDoc(doclist));
+
});
it('if the collection is empty, no docs should be selected', function () {
@@ -142,6 +148,17 @@
assert.notOk(store.areAllDocumentsSelected());
});
+ it('special mango docs are not selectable', function () {
+ store._collection = createMangoIndexDocColumn([
+ {ddoc: 'testId1', type: 'special', def: {fields: [{_id: 'desc'}]}},
+ {ddoc: 'testId2', blubb: 'ba', type: 'json', def: {fields: [{_id: 'desc'}]}}
+ ]);
+
+ store.selectAllDocuments();
+
+ assert.ok(store.areAllDocumentsSelected());
+ });
+
});
describe('canSelectAll', function () {
@@ -236,18 +253,25 @@
describe('#selectAllDocuments', function () {
it('selects all documents', function () {
- store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}], opts);
+ store._collection = createDocColumn([{_id: 'testId1', _rev: '1', 'value': 'one'}]);
store.selectAllDocuments();
assert.ok(store.getSelectedItems().testId1);
});
+ it('does not select all documents if rev is missing', function () {
+ store._collection = createDocColumn([{_id: 'testId1', 'value': 'one'}]);
+
+ store.selectAllDocuments();
+ assert.equal(store.getSelectedItemsLength(), 0);
+ });
+
});
describe('toggleSelectAllDocuments', function () {
it('deselects all documents', function () {
- store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}], opts);
+ store._collection = new Documents.AllDocs([{_id: 'testId1', _rev: '1', 'value': 'one'}], opts);
store.selectAllDocuments();
assert.ok(store.getSelectedItems().testId1);
@@ -257,7 +281,7 @@
it('deselects all documents', function () {
store.reset();
- store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}], opts);
+ store._collection = new Documents.AllDocs([{_id: 'testId1', _rev: '1', 'value': 'one'}], opts);
assert.equal(Object.keys(store.getSelectedItems()).length, 0);
store.toggleSelectAllDocuments();
diff --git a/app/addons/documents/resources.js b/app/addons/documents/resources.js
index c6cbb29..e8d291c 100644
--- a/app/addons/documents/resources.js
+++ b/app/addons/documents/resources.js
@@ -94,6 +94,10 @@
return this.get('type') !== 'special';
},
+ isBulkDeletable: function () {
+ return this.isDeletable();
+ },
+
isFromView: function () {
return false;
},
diff --git a/app/addons/documents/shared-resources.js b/app/addons/documents/shared-resources.js
index 7a635d9..570cef6 100644
--- a/app/addons/documents/shared-resources.js
+++ b/app/addons/documents/shared-resources.js
@@ -57,6 +57,10 @@
return app.utils.getDocTypeFromId(this.id);
},
+ isBulkDeletable: function () {
+ return !!this.id && !!this.get('_rev');
+ },
+
isDeletable: function () {
return !!this.id;
},
diff --git a/app/addons/documents/tests/document-test-helper.js b/app/addons/documents/tests/document-test-helper.js
new file mode 100644
index 0000000..c6ecce3
--- /dev/null
+++ b/app/addons/documents/tests/document-test-helper.js
@@ -0,0 +1,45 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+define([
+ 'api',
+ 'addons/documents/resources'
+], function (FauxtonAPI, Documents) {
+
+ var opts = {
+ params: {},
+ database: {
+ safeID: function () { return '1';}
+ }
+ };
+
+ function createDocColumn (docs) {
+ docs = docs.map(function (doc) {
+ return Documents.Doc.prototype.parse(doc);
+ });
+
+ return new Documents.AllDocs(docs, opts);
+ }
+
+ function createMangoIndexDocColumn (docs) {
+ docs = docs.map(function (doc) {
+ return Documents.MangoIndex.prototype.parse(doc);
+ });
+
+ return new Documents.MangoIndexCollection(docs, opts);
+ }
+
+ return {
+ createDocColumn: createDocColumn,
+ createMangoIndexDocColumn: createMangoIndexDocColumn
+ };
+
+});