Fix various saved search bugs, and add tests
Search condition ids are now indexed from 0, and always saved contiguously (no more 'fixGaps' option), since they're just in an array in the API. (They're still returned as an object from Zotero.Search.prototype.getConditions() because it's easier for the advanced search window to not have to deal with shifting ids between saves.)
This commit is contained in:
parent
d9c32a8e90
commit
4b040c78a7
|
@ -48,6 +48,12 @@ var ZoteroAdvancedSearch = new function() {
|
||||||
|
|
||||||
io.dataIn.search.loadPrimaryData()
|
io.dataIn.search.loadPrimaryData()
|
||||||
.then(function () {
|
.then(function () {
|
||||||
|
return Zotero.Groups.getAll();
|
||||||
|
})
|
||||||
|
.then(function (groups) {
|
||||||
|
// Since the search box can be used as a modal dialog, which can't use promises,
|
||||||
|
// it expects groups to be passed in.
|
||||||
|
_searchBox.groups = groups;
|
||||||
_searchBox.search = io.dataIn.search;
|
_searchBox.search = io.dataIn.search;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,8 @@
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
<implementation>
|
<implementation>
|
||||||
|
<property name="groups"/>
|
||||||
|
|
||||||
<field name="searchRef"/>
|
<field name="searchRef"/>
|
||||||
<property name="search" onget="return this.searchRef;">
|
<property name="search" onget="return this.searchRef;">
|
||||||
<setter>
|
<setter>
|
||||||
|
@ -49,28 +51,25 @@
|
||||||
conditionsBox.removeChild(conditionsBox.firstChild);
|
conditionsBox.removeChild(conditionsBox.firstChild);
|
||||||
|
|
||||||
var conditions = this.search.getConditions();
|
var conditions = this.search.getConditions();
|
||||||
|
for (let id in conditions) {
|
||||||
for(var id in conditions)
|
let condition = conditions[id];
|
||||||
{
|
|
||||||
// Checkboxes
|
// Checkboxes
|
||||||
switch (conditions[id]['condition']) {
|
switch (condition.condition) {
|
||||||
case 'recursive':
|
case 'recursive':
|
||||||
case 'noChildren':
|
case 'noChildren':
|
||||||
case 'includeParentsAndChildren':
|
case 'includeParentsAndChildren':
|
||||||
var checkbox = conditions[id]['condition'] + 'Checkbox';
|
let checkbox = condition.condition + 'Checkbox';
|
||||||
this.id(checkbox).setAttribute('condition',id);
|
this.id(checkbox).setAttribute('condition', id);
|
||||||
this.id(checkbox).checked = (conditions[id]['operator']=='true');
|
this.id(checkbox).checked = condition.operator == 'true';
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(conditions[id]['condition'] == 'joinMode')
|
if(condition.condition == 'joinMode') {
|
||||||
{
|
this.id('joinModeMenu').setAttribute('condition', id);
|
||||||
this.id('joinModeMenu').setAttribute('condition',id);
|
this.id('joinModeMenu').value = condition.operator;
|
||||||
this.id('joinModeMenu').value = conditions[id]['operator'];
|
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
{
|
this.addCondition(condition);
|
||||||
this.addCondition(conditions[id]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]]>
|
]]>
|
||||||
|
@ -96,9 +95,8 @@
|
||||||
menupopup.appendChild(menuitem);
|
menupopup.appendChild(menuitem);
|
||||||
|
|
||||||
// Add groups
|
// Add groups
|
||||||
var groups = Zotero.Groups.getAll();
|
for (let i = 0; i < this.groups.length; i++) {
|
||||||
for (let i=0; i<groups.length; i++) {
|
let group = this.groups[i];
|
||||||
let group = groups[i];
|
|
||||||
let menuitem = document.createElement('menuitem');
|
let menuitem = document.createElement('menuitem');
|
||||||
menuitem.setAttribute('label', group.name);
|
menuitem.setAttribute('label', group.name);
|
||||||
menuitem.setAttribute('libraryID', group.libraryID);
|
menuitem.setAttribute('libraryID', group.libraryID);
|
||||||
|
@ -232,7 +230,7 @@
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
this.updateSearch();
|
this.updateSearch();
|
||||||
return this.search.save({fixGaps: true});
|
return this.search.save();
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
|
@ -35,7 +35,9 @@ function doLoad()
|
||||||
|
|
||||||
io = window.arguments[0];
|
io = window.arguments[0];
|
||||||
|
|
||||||
document.getElementById('search-box').search = io.dataIn.search;
|
var searchBox = document.getElementById('search-box');
|
||||||
|
searchBox.groups = io.dataIn.groups;
|
||||||
|
searchBox.search = io.dataIn.search;
|
||||||
document.getElementById('search-name').value = io.dataIn.name;
|
document.getElementById('search-name').value = io.dataIn.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,8 +32,8 @@ Zotero.Search = function() {
|
||||||
this._scopeIncludeChildren = null;
|
this._scopeIncludeChildren = null;
|
||||||
this._sql = null;
|
this._sql = null;
|
||||||
this._sqlParams = false;
|
this._sqlParams = false;
|
||||||
this._maxSearchConditionID = 0;
|
this._maxSearchConditionID = -1;
|
||||||
this._conditions = [];
|
this._conditions = {};
|
||||||
this._hasPrimaryConditions = false;
|
this._hasPrimaryConditions = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,7 +179,6 @@ Zotero.Search.prototype._initSave = Zotero.Promise.coroutine(function* (env) {
|
||||||
});
|
});
|
||||||
|
|
||||||
Zotero.Search.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
|
Zotero.Search.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
|
||||||
var fixGaps = env.options.fixGaps;
|
|
||||||
var isNew = env.isNew;
|
var isNew = env.isNew;
|
||||||
|
|
||||||
var searchID = env.id = this._id = this.id ? this.id : yield Zotero.ID.get('savedSearches');
|
var searchID = env.id = this._id = this.id ? this.id : yield Zotero.ID.get('savedSearches');
|
||||||
|
@ -217,39 +216,28 @@ Zotero.Search.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
|
||||||
yield Zotero.DB.queryAsync(sql, this.id);
|
yield Zotero.DB.queryAsync(sql, this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close gaps in savedSearchIDs
|
var i = 0;
|
||||||
var saveConditions = {};
|
var sql = "INSERT INTO savedSearchConditions "
|
||||||
var i = 1;
|
+ "(savedSearchID, searchConditionID, condition, operator, value, required) "
|
||||||
for (var id in this._conditions) {
|
+ "VALUES (?,?,?,?,?,?)";
|
||||||
if (!fixGaps && id != i) {
|
for (let id in this._conditions) {
|
||||||
Zotero.DB.rollbackTransaction();
|
let condition = this._conditions[id];
|
||||||
throw ('searchConditionIDs not contiguous and |fixGaps| not set in save() of saved search ' + this._id);
|
|
||||||
}
|
|
||||||
saveConditions[i] = this._conditions[id];
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._conditions = saveConditions;
|
|
||||||
|
|
||||||
for (var i in this._conditions){
|
|
||||||
var sql = "INSERT INTO savedSearchConditions (savedSearchID, "
|
|
||||||
+ "searchConditionID, condition, operator, value, required) "
|
|
||||||
+ "VALUES (?,?,?,?,?,?)";
|
|
||||||
|
|
||||||
// Convert condition and mode to "condition[/mode]"
|
// Convert condition and mode to "condition[/mode]"
|
||||||
var condition = this._conditions[i].mode ?
|
let conditionString = condition.mode ?
|
||||||
this._conditions[i].condition + '/' + this._conditions[i].mode :
|
condition.condition + '/' + condition.mode :
|
||||||
this._conditions[i].condition
|
condition.condition
|
||||||
|
|
||||||
var sqlParams = [
|
var sqlParams = [
|
||||||
searchID,
|
searchID,
|
||||||
i,
|
i,
|
||||||
condition,
|
conditionString,
|
||||||
this._conditions[i].operator ? this._conditions[i].operator : null,
|
condition.operator ? condition.operator : null,
|
||||||
this._conditions[i].value ? this._conditions[i].value : null,
|
condition.value ? condition.value : null,
|
||||||
this._conditions[i].required ? 1 : null
|
condition.required ? 1 : null
|
||||||
];
|
];
|
||||||
yield Zotero.DB.queryAsync(sql, sqlParams);
|
yield Zotero.DB.queryAsync(sql, sqlParams);
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -274,6 +262,7 @@ Zotero.Search.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env)
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
yield this.loadPrimaryData(true);
|
||||||
yield this.reload();
|
yield this.reload();
|
||||||
this._clearChanged();
|
this._clearChanged();
|
||||||
|
|
||||||
|
@ -400,11 +389,13 @@ Zotero.Search.prototype.addCondition = function (condition, operator, value, req
|
||||||
mode: mode,
|
mode: mode,
|
||||||
operator: operator,
|
operator: operator,
|
||||||
value: value,
|
value: value,
|
||||||
required: required
|
required: !!required
|
||||||
};
|
};
|
||||||
|
|
||||||
this._sql = null;
|
this._sql = null;
|
||||||
this._sqlParams = false;
|
this._sqlParams = false;
|
||||||
|
this._markFieldChange('conditions', this._conditions);
|
||||||
|
this._changed.conditions = true;
|
||||||
return searchConditionID;
|
return searchConditionID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -426,8 +417,8 @@ Zotero.Search.prototype.setScope = function (searchObj, includeChildren) {
|
||||||
* @param {Boolean} [required]
|
* @param {Boolean} [required]
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
Zotero.Search.prototype.updateCondition = Zotero.Promise.coroutine(function* (searchConditionID, condition, operator, value, required){
|
Zotero.Search.prototype.updateCondition = function (searchConditionID, condition, operator, value, required) {
|
||||||
yield this.loadPrimaryData();
|
this._requireData('conditions');
|
||||||
|
|
||||||
if (typeof this._conditions[searchConditionID] == 'undefined'){
|
if (typeof this._conditions[searchConditionID] == 'undefined'){
|
||||||
throw new Error('Invalid searchConditionID ' + searchConditionID);
|
throw new Error('Invalid searchConditionID ' + searchConditionID);
|
||||||
|
@ -447,23 +438,27 @@ Zotero.Search.prototype.updateCondition = Zotero.Promise.coroutine(function* (se
|
||||||
mode: mode,
|
mode: mode,
|
||||||
operator: operator,
|
operator: operator,
|
||||||
value: value,
|
value: value,
|
||||||
required: required
|
required: !!required
|
||||||
};
|
};
|
||||||
|
|
||||||
this._sql = null;
|
this._sql = null;
|
||||||
this._sqlParams = false;
|
this._sqlParams = false;
|
||||||
});
|
this._markFieldChange('conditions', this._conditions);
|
||||||
|
this._changed.conditions = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Zotero.Search.prototype.removeCondition = Zotero.Promise.coroutine(function* (searchConditionID){
|
Zotero.Search.prototype.removeCondition = function (searchConditionID) {
|
||||||
yield this.loadPrimaryData();
|
this._requireData('conditions');
|
||||||
|
|
||||||
if (typeof this._conditions[searchConditionID] == 'undefined'){
|
if (typeof this._conditions[searchConditionID] == 'undefined'){
|
||||||
throw ('Invalid searchConditionID ' + searchConditionID + ' in removeCondition()');
|
throw ('Invalid searchConditionID ' + searchConditionID + ' in removeCondition()');
|
||||||
}
|
}
|
||||||
|
|
||||||
delete this._conditions[searchConditionID];
|
delete this._conditions[searchConditionID];
|
||||||
});
|
this._markFieldChange('conditions', this._conditions);
|
||||||
|
this._changed.conditions = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -896,38 +891,37 @@ Zotero.Search.prototype.loadConditions = Zotero.Promise.coroutine(function* (rel
|
||||||
this._maxSearchConditionID = conditions[conditions.length - 1].searchConditionID;
|
this._maxSearchConditionID = conditions[conditions.length - 1].searchConditionID;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reindex conditions, in case they're not contiguous in the DB
|
this._conditions = {};
|
||||||
var conditionID = 1;
|
|
||||||
|
|
||||||
|
// Reindex conditions, in case they're not contiguous in the DB
|
||||||
for (let i=0; i<conditions.length; i++) {
|
for (let i=0; i<conditions.length; i++) {
|
||||||
// Parse "condition[/mode]"
|
let condition = conditions[i];
|
||||||
var [condition, mode] =
|
|
||||||
Zotero.SearchConditions.parseCondition(conditions[i]['condition']);
|
|
||||||
|
|
||||||
var cond = Zotero.SearchConditions.get(condition);
|
// Parse "condition[/mode]"
|
||||||
|
let [conditionName, mode] = Zotero.SearchConditions.parseCondition(condition.condition);
|
||||||
|
|
||||||
|
let cond = Zotero.SearchConditions.get(conditionName);
|
||||||
if (!cond || cond.noLoad) {
|
if (!cond || cond.noLoad) {
|
||||||
Zotero.debug("Invalid saved search condition '" + condition + "' -- skipping", 2);
|
Zotero.debug("Invalid saved search condition '" + conditionName + "' -- skipping", 2);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert itemTypeID to itemType
|
// Convert itemTypeID to itemType
|
||||||
//
|
//
|
||||||
// TEMP: This can be removed at some point
|
// TEMP: This can be removed at some point
|
||||||
if (condition == 'itemTypeID') {
|
if (conditionName == 'itemTypeID') {
|
||||||
condition = 'itemType';
|
conditionName = 'itemType';
|
||||||
conditions[i].value = Zotero.ItemTypes.getName(conditions[i].value);
|
condition.value = Zotero.ItemTypes.getName(condition.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._conditions[conditionID] = {
|
this._conditions[i] = {
|
||||||
id: conditionID,
|
id: i,
|
||||||
condition: condition,
|
condition: conditionName,
|
||||||
mode: mode,
|
mode: mode,
|
||||||
operator: conditions[i].operator,
|
operator: condition.operator,
|
||||||
value: conditions[i].value,
|
value: condition.value,
|
||||||
required: conditions[i].required
|
required: !!condition.required
|
||||||
};
|
};
|
||||||
|
|
||||||
conditionID++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._loaded.conditions = true;
|
this._loaded.conditions = true;
|
||||||
|
|
|
@ -1809,7 +1809,7 @@ var ZoteroPane = new function()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
this.editSelectedCollection = function () {
|
this.editSelectedCollection = Zotero.Promise.coroutine(function* () {
|
||||||
if (!this.canEdit()) {
|
if (!this.canEdit()) {
|
||||||
this.displayCannotEditLibraryMessage();
|
this.displayCannotEditLibraryMessage();
|
||||||
return;
|
return;
|
||||||
|
@ -1832,22 +1832,32 @@ var ZoteroPane = new function()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var s = new Zotero.Search();
|
let s = new Zotero.Search();
|
||||||
s.id = row.ref.id;
|
s.id = row.ref.id;
|
||||||
s.loadPrimaryData()
|
yield s.loadPrimaryData();
|
||||||
.then(function () {
|
yield s.loadConditions();
|
||||||
return s.loadConditions();
|
let groups = [];
|
||||||
})
|
// Promises don't work in the modal dialog, so get the group name here, if
|
||||||
.then(function () {
|
// applicable, and pass it in. We only need the group that this search belongs
|
||||||
var io = {dataIn: {search: s, name: row.getName()}, dataOut: null};
|
// to, if any, since the library drop-down is disabled for saved searches.
|
||||||
window.openDialog('chrome://zotero/content/searchDialog.xul','','chrome,modal',io);
|
if (Zotero.Libraries.getType(s.libraryID) == 'group') {
|
||||||
if (io.dataOut) {
|
groups.push(yield Zotero.Groups.getByLibraryID(s.libraryID));
|
||||||
this.onCollectionSelected(); //reload itemsView
|
}
|
||||||
}
|
var io = {
|
||||||
}.bind(this));
|
dataIn: {
|
||||||
|
search: s,
|
||||||
|
name: row.getName(),
|
||||||
|
groups: groups
|
||||||
|
},
|
||||||
|
dataOut: null
|
||||||
|
};
|
||||||
|
window.openDialog('chrome://zotero/content/searchDialog.xul','','chrome,modal',io);
|
||||||
|
if (io.dataOut) {
|
||||||
|
this.onCollectionSelected(); //reload itemsView
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
|
|
||||||
this.copySelectedItemsToClipboard = function (asCitations) {
|
this.copySelectedItemsToClipboard = function (asCitations) {
|
||||||
|
|
|
@ -37,11 +37,61 @@ describe("Zotero.Search", function() {
|
||||||
yield s.loadConditions();
|
yield s.loadConditions();
|
||||||
var conditions = s.getConditions();
|
var conditions = s.getConditions();
|
||||||
assert.lengthOf(Object.keys(conditions), 1);
|
assert.lengthOf(Object.keys(conditions), 1);
|
||||||
assert.property(conditions, "1"); // searchConditionIDs start at 1
|
assert.property(conditions, "0");
|
||||||
var condition = conditions[1];
|
var condition = conditions[0];
|
||||||
assert.propertyVal(condition, 'condition', 'title')
|
assert.propertyVal(condition, 'condition', 'title')
|
||||||
assert.propertyVal(condition, 'operator', 'is')
|
assert.propertyVal(condition, 'operator', 'is')
|
||||||
assert.propertyVal(condition, 'value', 'test')
|
assert.propertyVal(condition, 'value', 'test')
|
||||||
|
assert.propertyVal(condition, 'required', false)
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add a condition to an existing search", function* () {
|
||||||
|
// Save search
|
||||||
|
var s = new Zotero.Search;
|
||||||
|
s.libraryID = Zotero.Libraries.userLibraryID;
|
||||||
|
s.name = "Test";
|
||||||
|
s.addCondition('title', 'is', 'test');
|
||||||
|
var id = yield s.save();
|
||||||
|
assert.typeOf(id, 'number');
|
||||||
|
|
||||||
|
// Add condition
|
||||||
|
s = yield Zotero.Searches.getAsync(id);
|
||||||
|
yield s.loadConditions();
|
||||||
|
s.addCondition('title', 'contains', 'foo');
|
||||||
|
var saved = yield s.save();
|
||||||
|
assert.isTrue(saved);
|
||||||
|
|
||||||
|
// Check saved search
|
||||||
|
s = yield Zotero.Searches.getAsync(id);
|
||||||
|
yield s.loadConditions();
|
||||||
|
var conditions = s.getConditions();
|
||||||
|
assert.lengthOf(Object.keys(conditions), 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove a condition from an existing search", function* () {
|
||||||
|
// Save search
|
||||||
|
var s = new Zotero.Search;
|
||||||
|
s.libraryID = Zotero.Libraries.userLibraryID;
|
||||||
|
s.name = "Test";
|
||||||
|
s.addCondition('title', 'is', 'test');
|
||||||
|
s.addCondition('title', 'contains', 'foo');
|
||||||
|
var id = yield s.save();
|
||||||
|
assert.typeOf(id, 'number');
|
||||||
|
|
||||||
|
// Remove condition
|
||||||
|
s = yield Zotero.Searches.getAsync(id);
|
||||||
|
yield s.loadConditions();
|
||||||
|
s.removeCondition(0);
|
||||||
|
var saved = yield s.save();
|
||||||
|
assert.isTrue(saved);
|
||||||
|
|
||||||
|
// Check saved search
|
||||||
|
s = yield Zotero.Searches.getAsync(id);
|
||||||
|
yield s.loadConditions();
|
||||||
|
var conditions = s.getConditions();
|
||||||
|
assert.lengthOf(Object.keys(conditions), 1);
|
||||||
|
assert.property(conditions, "0");
|
||||||
|
assert.propertyVal(conditions[0], 'value', 'foo')
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user