Show invalid-data warning in sync button tooltip for group libraries

Instead of showing warning button on toolbar

Otherwise if one person in a group upgrades to a beta with new fields,
everyone in the group will get a warning, even if they're already on the
latest release version. In a personal library, the user can upgrade to
the same version.
This commit is contained in:
Dan Stillman 2021-02-09 16:36:06 -05:00
parent b919143630
commit 843bcbb68a
5 changed files with 238 additions and 32 deletions

View file

@ -32,6 +32,7 @@ if (!Zotero.Sync) {
// Initialized as Zotero.Sync.Runner in zotero.js
Zotero.Sync.Runner_Module = function (options = {}) {
const stopOnError = false;
const HTML_NS = 'http://www.w3.org/1999/xhtml';
Zotero.defineProperty(this, 'enabled', {
get: () => {
@ -84,7 +85,9 @@ Zotero.Sync.Runner_Module = function (options = {}) {
var _lastSyncStatus;
var _currentSyncStatusLabel;
var _currentLastSyncLabel;
var _currentTooltipMessages;
var _errors = [];
var _tooltipMessages = [];
Zotero.addShutdownListener(() => this.stop());
@ -118,6 +121,7 @@ Zotero.Sync.Runner_Module = function (options = {}) {
this._sync = Zotero.Promise.coroutine(function* (options) {
// Clear message list
_errors = [];
_tooltipMessages = [];
// Shouldn't be possible because of serial()
if (_syncInProgress) {
@ -1033,7 +1037,8 @@ Zotero.Sync.Runner_Module = function (options = {}) {
upgrade: 4,
// Skip these
animate: -1
animate: -1,
ignore: -2
};
var state = false;
for (let i = 0; i < errors.length; i++) {
@ -1265,20 +1270,31 @@ Zotero.Sync.Runner_Module = function (options = {}) {
}
// Show warning for unknown data that couldn't be saved
else if (e.name && e.name == 'ZoteroInvalidDataError') {
e.message = Zotero.getString(
'sync.error.invalidDataError',
[
Zotero.Libraries.get(e.libraryID).name,
Zotero.clientName
]
)
+ "\n\n"
+ Zotero.getString('sync.error.invalidDataError.otherData');
e.errorType = 'warning';
e.dialogButtonText = Zotero.getString('general.checkForUpdates');
e.dialogButtonCallback = () => {
Zotero.openCheckForUpdatesWindow();
};
let library = Zotero.Libraries.get(e.libraryID);
let msg = Zotero.getString(
'sync.error.invalidDataError',
[
library.name,
Zotero.clientName
]
)
+ "\n\n"
+ Zotero.getString('sync.error.invalidDataError.otherData');
// Show warning for My Library
if (library.libraryType == 'user') {
e.message = msg;
e.errorType = 'warning';
e.dialogButtonText = Zotero.getString('general.checkForUpdates');
e.dialogButtonCallback = () => {
Zotero.openCheckForUpdatesWindow();
};
}
// Otherwise just show in sync button tooltip
else {
_addTooltipMessage(msg);
e.errorType = 'ignore';
}
}
});
@ -1299,6 +1315,7 @@ Zotero.Sync.Runner_Module = function (options = {}) {
if (!Array.isArray(errors)) {
errors = [errors];
}
errors = errors.filter(o => o.errorType !== 'ignore');
var state = this.getPrimaryErrorType(errors);
}
@ -1512,10 +1529,12 @@ Zotero.Sync.Runner_Module = function (options = {}) {
if (tooltip) {
_currentSyncStatusLabel = tooltip.firstChild.nextSibling;
_currentLastSyncLabel = tooltip.firstChild.nextSibling.nextSibling;
_currentTooltipMessages = tooltip.querySelector('.sync-button-tooltip-messages');
}
else {
_currentSyncStatusLabel = null;
_currentLastSyncLabel = null;
_currentTooltipMessages = null;
}
if (_currentSyncStatusLabel) {
_updateSyncStatusLabel();
@ -1547,7 +1566,12 @@ Zotero.Sync.Runner_Module = function (options = {}) {
Zotero.Sync.Data.Local.setAPIKey();
yield client.deleteAPIKey();
})
function _addTooltipMessage(msg) {
_tooltipMessages.push(msg.replace(/\n+/g, ' '));
};
function _updateSyncStatusLabel() {
if (_lastSyncStatus) {
@ -1585,6 +1609,19 @@ Zotero.Sync.Runner_Module = function (options = {}) {
_currentLastSyncLabel.value = Zotero.getString('sync.status.lastSync') + " " + msg;
_currentLastSyncLabel.hidden = false;
if (_tooltipMessages.length) {
_currentTooltipMessages.textContent = '';
for (let message of _tooltipMessages) {
let elem = _currentTooltipMessages.ownerDocument.createElementNS(HTML_NS, 'p');
elem.textContent = message;
_currentTooltipMessages.appendChild(elem);
}
_currentTooltipMessages.hidden = false;
}
else {
_currentTooltipMessages.hidden = true;
}
}

View file

@ -214,6 +214,7 @@
<label id="zotero-tb-sync-label"/>
<label id="zotero-tb-sync-status" hidden="true"/>
<label id="zotero-tb-sync-last-sync"/>
<div xmlns="http://www.w3.org/1999/xhtml" class="sync-button-tooltip-messages"/>
</tooltip>
</toolbarbutton>
</hbox>

View file

@ -27,5 +27,6 @@
@import "components/icons";
@import "components/progressMeter";
@import "components/search";
@import "components/syncButtonTooltip";
@import "components/tagsBox";
@import "components/tagSelector";

View file

@ -0,0 +1,8 @@
.sync-button-tooltip-messages {
max-width: 400px;
p {
color: gray;
margin: 8px 6px;
}
}

View file

@ -109,6 +109,63 @@ describe("Zotero.Sync.Runner", function () {
setHTTPResponse(server, baseURL, response, responses);
}
function setDefaultResponses(options = {}) {
var target = options.target || 'users/1';
var headers = {
"Last-Modified-Version": options.libraryVersion || 5
};
var lastLibraryVersion = options.lastLibraryVersion || 4;
setResponse({
method: "GET",
url: `${target}/settings?since=${lastLibraryVersion}`,
status: 200,
headers,
json: {}
});
setResponse({
method: "GET",
url: `${target}/collections?format=versions&since=${lastLibraryVersion}`,
status: 200,
headers,
json: {}
});
setResponse({
method: "GET",
url: `${target}/searches?format=versions&since=${lastLibraryVersion}`,
status: 200,
headers,
json: {}
});
setResponse({
method: "GET",
url: `${target}/items/top?format=versions&since=${lastLibraryVersion}&includeTrashed=1`,
status: 200,
headers,
json: {}
});
setResponse({
method: "GET",
url: `${target}/items?format=versions&since=${lastLibraryVersion}&includeTrashed=1`,
status: 200,
headers,
json: {}
});
setResponse({
method: "GET",
url: `${target}/deleted?since=${lastLibraryVersion}`,
status: 200,
headers,
json: {}
});
setResponse({
method: "GET",
url: `${target}/fulltext?format=versions`,
status: 200,
headers,
json: {}
});
}
//
// Tests
@ -993,28 +1050,18 @@ describe("Zotero.Sync.Runner", function () {
}
});
it("should show the sync error icon on error", function* () {
it("should show the sync error icon on error", async function () {
let library = Zotero.Libraries.userLibrary;
library.libraryVersion = 5;
yield library.save();
library.libraryVersion = 1;
await library.save();
setResponse('keyInfo.fullAccess');
setResponse('userGroups.groupVersionsEmpty');
// My Library
setResponse({
method: "GET",
url: "users/1/settings",
status: 200,
headers: {
"Last-Modified-Version": 5
},
json: {
INVALID: true // TODO: Find a cleaner error
}
});
// No other responses, so settings response will be a 404
spy = sinon.spy(runner, "updateIcons");
yield runner.sync();
await runner.sync();
assert.isTrue(spy.calledTwice);
assert.isArray(spy.args[1][0]);
assert.lengthOf(spy.args[1][0], 1);
@ -1097,6 +1144,118 @@ describe("Zotero.Sync.Runner", function () {
});
it("should show an error for invalid My Library data", async function () {
let library = Zotero.Libraries.userLibrary;
library.libraryVersion = 1;
await library.save();
var collection = await createDataObject('collection', { synced: true });
var json = collection.toResponseJSON();
json.version = json.data.version = 2;
json.data.INVALID = true;
setResponse('keyInfo.fullAccess');
setResponse('userGroups.groupVersionsEmpty');
setDefaultResponses({
lastLibraryVersion: 1,
libraryVersion: 2
});
setResponse({
method: "GET",
url: "users/1/collections?format=versions&since=1",
status: 200,
headers: {
"Last-Modified-Version": 2
},
json: {
[json.key]: 2
}
});
setResponse({
method: "GET",
url: `users/1/collections?format=json&collectionKey=${json.key}`,
status: 200,
headers: {
"Last-Modified-Version": 2
},
json: [json]
});
spy = sinon.spy(runner, "updateIcons");
await runner.sync();
assert.isTrue(spy.calledTwice);
assert.isArray(spy.args[1][0]);
assert.lengthOf(spy.args[1][0], 1);
// Not an instance of Error for some reason
var error = spy.args[1][0][0];
assert.equal(Object.getPrototypeOf(error).constructor.name, "Error");
assert.match(error.message, /^Some data in My Library/);
});
it("should show a warning in the sync button tooltip for invalid group data", async function () {
win = await loadZoteroPane();
var doc = win.document;
// Create group with same id and version as groups response
var groupData = responses.groups.memberGroup;
var group = await createGroup({
id: groupData.json.id,
version: groupData.json.version
});
group.libraryVersion = 1;
await group.save();
var collection = await createDataObject('collection', { synced: true });
var json = collection.toResponseJSON();
json.version = json.data.version = 2;
json.data.INVALID = true;
var target = 'groups/' + group.id;
setResponse('keyInfo.fullAccess');
setResponse('userGroups.groupVersionsOnlyMemberGroup');
setResponse('groups.memberGroup');
setDefaultResponses({
target,
lastLibraryVersion: 1,
libraryVersion: 2
});
setResponse({
method: "GET",
url: target + '/collections?format=versions&since=1',
status: 200,
headers: {
"Last-Modified-Version": 2
},
json: {
[json.key]: 2
}
});
setResponse({
method: "GET",
url: target + `/collections?format=json&collectionKey=${json.key}`,
status: 200,
headers: {
"Last-Modified-Version": 2
},
json: [json]
});
await runner.sync({ libraries: [group.libraryID] });
assert.isTrue(doc.getElementById('zotero-tb-sync-error').hidden);
// Fake what happens on button mouseover
var tooltip = doc.getElementById('zotero-tb-sync-tooltip');
runner.registerSyncStatus(tooltip);
var html = doc.getElementById('zotero-tb-sync-tooltip').innerHTML;
assert.match(html, /Some data in .+\. Other data will continue to sync\./);
runner.registerSyncStatus();
});
// TODO: Test multiple long tags and tags across libraries
describe("Long Tag Fixer", function () {
it("should split a tag", function* () {