Add flags to disable retraction warnings

Separate flags for hiding the retraction altogether and for hiding
citation warnings for it

New functions:

Zotero.Retractions.hideRetraction(item)
Zotero.Retractions.shouldShowCitationWarning(item)
Zotero.Retractions.disableCitationWarningsForItem(item)

Addresses #1710
This commit is contained in:
Dan Stillman 2019-07-03 01:23:02 -04:00
parent f49d5805cd
commit 0beddb9680
5 changed files with 126 additions and 15 deletions

View file

@ -1039,7 +1039,7 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
} }
if (retracted) { if (retracted) {
sql += " AND (itemID IN (SELECT itemID FROM retractedItems))"; sql += " AND (itemID IN (SELECT itemID FROM retractedItems WHERE flag=0))";
} }
if (publications) { if (publications) {

View file

@ -28,6 +28,10 @@ Zotero.Retractions = {
TYPE_PMID: 'p', TYPE_PMID: 'p',
TYPE_NAMES: ['DOI', 'PMID'], TYPE_NAMES: ['DOI', 'PMID'],
FLAG_NORMAL: 0,
FLAG_HIDDEN: 1,
FLAG_NO_CITATION_WARNING: 2,
_prefObserverRegistered: false, _prefObserverRegistered: false,
_initialized: false, _initialized: false,
_version: 1, _version: 1,
@ -60,13 +64,13 @@ Zotero.Retractions = {
// Load existing retracted items // Load existing retracted items
var rows = await Zotero.DB.queryAsync( var rows = await Zotero.DB.queryAsync(
"SELECT libraryID, itemID, DI.itemID IS NOT NULL AS deleted FROM items " "SELECT libraryID, itemID, DI.itemID IS NOT NULL AS deleted, RI.flag FROM items "
+ "JOIN retractedItems USING (itemID) " + "JOIN retractedItems RI USING (itemID) "
+ "LEFT JOIN deletedItems DI USING (itemID)" + "LEFT JOIN deletedItems DI USING (itemID)"
); );
for (let row of rows) { for (let row of rows) {
this._retractedItems.add(row.itemID); this._retractedItems.set(row.itemID, row.flag);
if (!row.deleted) { if (!row.deleted && row.flag != this.FLAG_HIDDEN) {
if (!this._retractedItemsByLibrary[row.libraryID]) { if (!this._retractedItemsByLibrary[row.libraryID]) {
this._retractedItemsByLibrary[row.libraryID] = new Set(); this._retractedItemsByLibrary[row.libraryID] = new Set();
} }
@ -88,7 +92,7 @@ Zotero.Retractions = {
this._itemKeys = {}; this._itemKeys = {};
this._queuedItems = new Set(); this._queuedItems = new Set();
this._queuedPrefixStrings = new Set(); this._queuedPrefixStrings = new Set();
this._retractedItems = new Set(); this._retractedItems = new Map();
this._retractedItemsByLibrary = {}; this._retractedItemsByLibrary = {};
this._librariesWithRetractions = new Set(); this._librariesWithRetractions = new Set();
this._cacheVersion = null; this._cacheVersion = null;
@ -99,11 +103,53 @@ Zotero.Retractions = {
}, },
/** /**
* @param {Zotero.Item} * If item was retracted and the retraction hasn't been hidden
*
* @param {Zotero.Item} item
* @return {Boolean} * @return {Boolean}
*/ */
isRetracted: function (item) { isRetracted: function (item) {
return this._retractedItems.has(item.id); var flag = this._retractedItems.get(item.id);
return flag !== undefined && flag !== this.FLAG_HIDDEN;
},
/**
* If item was retracted and hasn't been marked to not show citation warnings
*
* @param {Zotero.Item}
* @return {Boolean}
*/
shouldShowCitationWarning: function (item) {
return this._retractedItems.get(item.id) === this.FLAG_NORMAL;
},
/**
* Don't show any future retraction warnings for this item
*
* @param {Zotero.Item} item
* @return {Promise}
*/
hideRetraction: async function (item) {
return this._updateItemFlag(item, this.FLAG_HIDDEN);
},
/**
* Don't show future citation warnings for this item
*
* @param {Zotero.Item} item
* @return {Promise}
*/
disableCitationWarningsForItem: async function (item) {
return this._updateItemFlag(item, this.FLAG_NO_CITATION_WARNING);
},
_updateItemFlag: async function (item, flag) {
this._retractedItems.set(item.id, flag);
await Zotero.DB.queryAsync(
"UPDATE retractedItems SET flag=? WHERE itemID=?",
[flag, item.id]
);
await Zotero.Notifier.trigger('modify', 'item', [item.id]);
}, },
getRetractionsFromJSON: Zotero.serial(async function (jsonItems) { getRetractionsFromJSON: Zotero.serial(async function (jsonItems) {
@ -369,8 +415,9 @@ Zotero.Retractions = {
// handled for newly detected items in _addEntry(), which gets called by // handled for newly detected items in _addEntry(), which gets called by
// _updateItem() above after a delay (such that the item won't yet be retracted // _updateItem() above after a delay (such that the item won't yet be retracted
// here). // here).
if (this._retractedItems.has(item.id)) { let flag = this._retractedItems.get(item.id);
if (item.deleted) { if (flag !== undefined) {
if (item.deleted || flag == this.FLAG_HIDDEN) {
await this._removeLibraryRetractedItem(item.libraryID, item.id); await this._removeLibraryRetractedItem(item.libraryID, item.id);
} }
else { else {
@ -577,7 +624,7 @@ Zotero.Retractions = {
// Remove existing retracted items that no longer match // Remove existing retracted items that no longer match
var removed = 0; var removed = 0;
if (removeExisting) { if (removeExisting) {
for (let itemID of this._retractedItems) { for (let itemID of this._retractedItems.keys()) {
if (!allItemIDs.has(itemID)) { if (!allItemIDs.has(itemID)) {
let item = await Zotero.Items.getAsync(itemID); let item = await Zotero.Items.getAsync(itemID);
await this._removeEntry(itemID, item.libraryID); await this._removeEntry(itemID, item.libraryID);
@ -800,13 +847,17 @@ Zotero.Retractions = {
delete o.retractionDOI; delete o.retractionDOI;
delete o.retractionPMID; delete o.retractionPMID;
var sql = "REPLACE INTO retractedItems VALUES (?, ?)"; var sql = "REPLACE INTO retractedItems (itemID, data) VALUES (?, ?)";
await Zotero.DB.queryAsync(sql, [itemID, JSON.stringify(o)]); await Zotero.DB.queryAsync(sql, [itemID, JSON.stringify(o)]);
var item = await Zotero.Items.getAsync(itemID); var item = await Zotero.Items.getAsync(itemID);
var libraryID = item.libraryID; var libraryID = item.libraryID;
this._retractedItems.add(itemID); // Check whether the retraction is already hidden by the user
if (!item.deleted) { var flag = this._retractedItems.get(itemID);
if (flag === undefined) {
this._retractedItems.set(itemID, this.FLAG_NORMAL);
}
if (!item.deleted && flag !== this.FLAG_HIDDEN) {
if (!this._retractedItemsByLibrary[libraryID]) { if (!this._retractedItemsByLibrary[libraryID]) {
this._retractedItemsByLibrary[libraryID] = new Set(); this._retractedItemsByLibrary[libraryID] = new Set();
} }

View file

@ -2518,6 +2518,10 @@ Zotero.Schema = new function(){
yield Zotero.DB.queryAsync("CREATE TABLE IF NOT EXISTS retractedItems (\n itemID INTEGER PRIMARY KEY,\n data TEXT,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE\n);"); yield Zotero.DB.queryAsync("CREATE TABLE IF NOT EXISTS retractedItems (\n itemID INTEGER PRIMARY KEY,\n data TEXT,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE\n);");
} }
else if (i == 105) {
yield Zotero.DB.queryAsync("ALTER TABLE retractedItems ADD COLUMN flag INT DEFAULT 0");
}
// If breaking compatibility or doing anything dangerous, clear minorUpdateFrom // If breaking compatibility or doing anything dangerous, clear minorUpdateFrom
} }

View file

@ -1,4 +1,4 @@
-- 104 -- 105
-- Copyright (c) 2009 Center for History and New Media -- Copyright (c) 2009 Center for History and New Media
-- George Mason University, Fairfax, Virginia, USA -- George Mason University, Fairfax, Virginia, USA
@ -289,6 +289,7 @@ CREATE TABLE publicationsItems (
CREATE TABLE retractedItems ( CREATE TABLE retractedItems (
itemID INTEGER PRIMARY KEY, itemID INTEGER PRIMARY KEY,
data TEXT, data TEXT,
flag INT DEFAULT 0,
FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE
); );

View file

@ -174,6 +174,23 @@ describe("Retractions", function() {
}); });
describe("#shouldShowCitationWarning()", function () {
it("should return false if citation warning is hidden", async function () {
var item = await createRetractedItem();
assert.isTrue(Zotero.Retractions.shouldShowCitationWarning(item));
await Zotero.Retractions.disableCitationWarningsForItem(item);
assert.isFalse(Zotero.Retractions.shouldShowCitationWarning(item));
});
it("should return false if retraction is hidden", async function () {
var item = await createRetractedItem();
assert.isTrue(Zotero.Retractions.shouldShowCitationWarning(item));
await Zotero.Retractions.hideRetraction(item);
assert.isFalse(Zotero.Retractions.shouldShowCitationWarning(item));
});
});
describe("#getRetractionsFromJSON()", function () { describe("#getRetractionsFromJSON()", function () {
it("should identify object with retracted DOI", async function () { it("should identify object with retracted DOI", async function () {
var spy = sinon.spy(Zotero.HTTP, 'request'); var spy = sinon.spy(Zotero.HTTP, 'request');
@ -275,6 +292,44 @@ describe("Retractions", function() {
assert.equal(zp.collectionsView.selectedTreeRow.id, "L" + userLibraryID); assert.equal(zp.collectionsView.selectedTreeRow.id, "L" + userLibraryID);
}); });
it("should hide Retracted Items collection when last retracted item is marked as hidden", async function () {
var rowID = "R" + userLibraryID;
// Create item
var item = await createRetractedItem();
assert.ok(zp.collectionsView.getRowIndexByID(rowID));
// Select Retracted Items collection
await zp.collectionsView.selectByID(rowID);
await waitForItemsLoad(win);
await Zotero.Retractions.hideRetraction(item);
await Zotero.Promise.delay(50);
// Retracted Items should be gone
assert.isFalse(zp.collectionsView.getRowIndexByID(rowID));
// And My Library should be selected
assert.equal(zp.collectionsView.selectedTreeRow.id, "L" + userLibraryID);
});
it("shouldn't hide Retracted Items collection when last retracted item is marked to not show a citation warning", async function () {
var rowID = "R" + userLibraryID;
// Create item
var item = await createRetractedItem();
assert.ok(zp.collectionsView.getRowIndexByID(rowID));
// Select Retracted Items collection
await zp.collectionsView.selectByID(rowID);
await waitForItemsLoad(win);
await Zotero.Retractions.disableCitationWarningsForItem(item);
await Zotero.Promise.delay(50);
// Should still be showing
assert.ok(zp.collectionsView.getRowIndexByID("R" + userLibraryID));
});
it("should show Retracted Items collection when retracted item is restored from trash", async function () { it("should show Retracted Items collection when retracted item is restored from trash", async function () {
// Create trashed item // Create trashed item
var item = await createRetractedItem({ deleted: true }); var item = await createRetractedItem({ deleted: true });