Add mechanism for showing dialog with notice from repo

- Messages are shown once a day by default (within the same session for
  id-less messages)
- Messages with an `id` attribute include a checkbox to not show again
  for 30 days
- If an `infoURL` attribute is provided, a "More Information" button is
  shown that launches that URL
- If `title` is provided, it's used for the dialog title. Otherwise
  "Warning" is shown.
This commit is contained in:
Dan Stillman 2021-08-29 03:31:41 -04:00
parent ba7d0a18aa
commit 2459614f04
2 changed files with 261 additions and 0 deletions

View file

@ -53,6 +53,7 @@ Zotero.Schema = new function(){
var _nextRepositoryUpdate;
var _remoteUpdateInProgress = false;
var _localUpdateInProgress = false;
var _hiddenNoticesWithoutIDs = new Map();
var self = this;
@ -2364,6 +2365,8 @@ Zotero.Schema = new function(){
var translatorUpdates = xmlhttp.responseXML.getElementsByTagName('translator');
var styleUpdates = xmlhttp.responseXML.getElementsByTagName('style');
_showRepositoryMessage(xmlhttp.responseXML);
if (!translatorUpdates.length && !styleUpdates.length){
await Zotero.DB.executeTransaction(function* (conn) {
// Store the timestamp provided by the server
@ -2415,6 +2418,111 @@ Zotero.Schema = new function(){
}
/**
* Show dialog if repo returns a message
*/
function _showRepositoryMessage(responseXML) {
try {
var messageElem = responseXML.querySelector('message');
if (!messageElem || !messageElem.textContent) {
return;
}
let hiddenNotices = Zotero.Prefs.get('hiddenNotices') || '{}';
try {
hiddenNotices = JSON.parse(hiddenNotices);
}
catch (e) {
Zotero.logError(e);
hiddenNotices = {};
}
let id = messageElem.getAttribute('id');
let title = messageElem.getAttribute('title');
let text = messageElem.textContent;
let url = messageElem.getAttribute('infoURL');
let now = Math.round(Date.now() / 1000);
let thirtyDays = 86400 * 30;
if (id) {
if (hiddenNotices[id] && hiddenNotices[id] > now) {
Zotero.debug("Not showing hidden notice " + id, 2);
Zotero.debug(text, 2);
return;
}
}
else {
Zotero.debug("CHECKING");
let exp = _hiddenNoticesWithoutIDs.get(text);
Zotero.debug(exp);
Zotero.debug(now);
if (exp && exp > now) {
Zotero.debug("Not showing hidden notice", 2);
Zotero.debug(text, 2);
return;
}
}
setTimeout(() => {
Zotero.debug(text, 2);
var ps = Services.prompt;
var buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_OK
+ ps.BUTTON_POS_1 * ps.BUTTON_TITLE_IS_STRING;
var checkState = {};
var index = ps.confirmEx(
null,
title || Zotero.getString('general.warning'),
text,
buttonFlags,
"",
// Show "More Information" button if repo includes a URL
url ? Zotero.getString('general.moreInformation') : "",
"",
// Show "Don't show again for 30 days" if repo includes an id
id ? Zotero.getString('general.dontShowAgainFor', 30, 30) : null,
checkState
);
if (index == 1) {
setTimeout(function () {
Zotero.launchURL(url);
}, 1);
}
// Handle "Don't show again for 30 days" checkbox
if (id) {
if (checkState.value) {
hiddenNotices[id] = now + thirtyDays;
}
// If not checked, still don't show again for a day
else {
hiddenNotices[id] = now + 86400;
}
// Remove expired hidden notices
for (let i in hiddenNotices) {
if (hiddenNotices[i] < now) {
delete hiddenNotices[i];
}
}
if (Object.keys(hiddenNotices).length) {
Zotero.Prefs.set('hiddenNotices', JSON.stringify(hiddenNotices));
}
else {
Zotero.Prefs.clear('hiddenNotices');
}
}
else {
// Don't show id-less messages again for a day
_hiddenNoticesWithoutIDs.set(text, now + 86400);
}
}, 500);
}
catch (e) {
Zotero.logError(e);
}
}
/**
* Set the interval between repository queries
*

View file

@ -240,6 +240,159 @@ describe("Zotero.Schema", function() {
});
describe("Repository Check", function () {
describe("Notices", function () {
var win;
var server;
before(async function () {
win = await loadZoteroPane();
});
beforeEach(function () {
Zotero.HTTP.mock = sinon.FakeXMLHttpRequest;
server = sinon.fakeServer.create();
server.autoRespond = true;
});
afterEach(function () {
Zotero.Prefs.clear('hiddenNotices');
});
after(function () {
win.close();
Zotero.HTTP.mock = null;
});
function createResponseWithMessage(message) {
server.respond(function (req) {
if (req.method != "POST" || !req.url.includes('/repo/updated')) {
return;
}
req.respond(
200,
{
"Content-Type": "application/xml"
},
'<xml>'
+ '<currentTime>1630219842</currentTime>'
+ message
+ '</xml>'
);
});
}
it("should show dialog if repo returns a message", async function () {
createResponseWithMessage(
`<message infoURL="https://example.com">This is a warning</message>`
);
var promise = waitForDialog(function (dialog) {
var html = dialog.document.documentElement.outerHTML;
assert.include(html, "This is a warning");
});
await Zotero.Schema.updateFromRepository(3);
await promise;
// Don't show id-less message again for a day
var spy = sinon.spy(Zotero, 'debug');
await Zotero.Schema.updateFromRepository(3);
assert.notEqual(spy.args.findIndex(x => {
return typeof x[0] == 'string' && x[0].startsWith("Not showing hidden");
}), -1);
spy.restore();
});
it("shouldn't show message with id again for 1 day even if not hidden", async function () {
var id = Zotero.Utilities.randomString();
createResponseWithMessage(
`<message id="${id}" infoURL="https://example.com">This is a warning</message>`
);
var promise = waitForDialog();
await Zotero.Schema.updateFromRepository(3);
await promise;
// Make sure notice is hidden for 1 day
var hiddenNotices;
var tries = 0;
var ttl = 86400;
while (tries < 100) {
tries++;
hiddenNotices = Zotero.Prefs.get('hiddenNotices');
if (!hiddenNotices) {
await Zotero.Promise.delay(10);
continue;
}
hiddenNotices = JSON.parse(hiddenNotices);
assert.property(hiddenNotices, id);
assert.approximately(hiddenNotices[id], Math.round(Date.now() / 1000) + ttl, 10);
break;
}
});
it("shouldn't show message with id again for 30 days", async function () {
var id = Zotero.Utilities.randomString();
createResponseWithMessage(
`<message id="${id}" infoURL="https://example.com">This is a warning</message>`
);
var promise = waitForDialog(function (dialog) {
var doc = dialog.document;
var innerHTML = doc.documentElement.innerHTML;
assert.include(innerHTML, "This is a warning");
assert.include(innerHTML, Zotero.getString('general.dontShowAgainFor', 30, 30));
// Check "Don't show again"
doc.getElementById('checkbox').click();
});
await Zotero.Schema.updateFromRepository(3);
await promise;
// Make sure notice is hidden for 30 days
var hiddenNotices;
var tries = 0;
var ttl = 30 * 86400;
while (tries < 100) {
tries++;
hiddenNotices = Zotero.Prefs.get('hiddenNotices');
if (!hiddenNotices) {
await Zotero.Promise.delay(10);
continue;
}
hiddenNotices = JSON.parse(hiddenNotices);
assert.property(hiddenNotices, id);
assert.approximately(hiddenNotices[id], Math.round(Date.now() / 1000) + ttl, 10);
break;
}
});
it("shouldn't show message with id if before expiration", async function () {
var id = Zotero.Utilities.randomString();
createResponseWithMessage(
`<message id="${id}" infoURL="https://example.com">This is a warning</message>`
);
// Set expiration for 30 days from now
var ttl = 30 * 86400;
Zotero.Prefs.set(
'hiddenNotices',
JSON.stringify({
[id]: Math.round(Date.now() / 1000) + ttl
})
);
// Message should be hidden
var spy = sinon.spy(Zotero, 'debug');
await Zotero.Schema.updateFromRepository(3);
assert.notEqual(spy.args.findIndex(x => {
return typeof x[0] == 'string' && x[0].startsWith("Not showing hidden");
}), -1);
spy.restore();
});
});
});
describe("#integrityCheck()", function () {
before(function* () {
yield resetDB({