Merge pull request #13118 from dannon/news-gtn-hooks-safety

Webhooks robustification for news and gtn
This commit is contained in:
Marius van den Beek
2022-01-06 17:26:40 +01:00
committed by GitHub
2 changed files with 256 additions and 245 deletions
+165 -159
View File
@@ -1,84 +1,89 @@
var gtnWebhookLoaded = false;
var lastUpdate = 0;
(function () {
var gtnWebhookLoaded = false;
var lastUpdate = 0;
function removeOverlay() {
document.getElementById("gtn-container").style.visibility = "hidden";
}
function showOverlay() {
document.getElementById("gtn-container").style.visibility = "visible";
}
function getIframeUrl() {
var loc;
try {
loc = document.getElementById("gtn-embed").contentWindow.location.pathname;
} catch (e) {
loc = null;
function removeOverlay() {
const container = document.getElementById("gtn-container");
if (container) {
container.style.visibility = "hidden";
}
}
return loc;
}
function getIframeScroll() {
var loc;
try {
loc = parseInt(document.getElementById("gtn-embed").contentWindow.scrollY);
} catch (e) {
loc = 0;
function showOverlay() {
document.getElementById("gtn-container").style.visibility = "visible";
}
return loc;
}
function restoreLocation() {}
function persistLocation() {
// Don't save every scroll event.
var time = new Date().getTime();
if (time - lastUpdate < 1000) {
return;
function getIframeUrl() {
var loc;
try {
loc = document.getElementById("gtn-embed").contentWindow.location.pathname;
} catch (e) {
loc = null;
}
return loc;
}
lastUpdate = time;
window.localStorage.setItem("gtn-in-galaxy", `${getIframeScroll()} ${getIframeUrl()}`);
}
function addIframe() {
let url, message, onloadscroll;
gtnWebhookLoaded = true;
let storedData = false;
let safe = false;
function getIframeScroll() {
var loc;
try {
loc = parseInt(document.getElementById("gtn-embed").contentWindow.scrollY);
} catch (e) {
loc = 0;
}
return loc;
}
// Test for the presence of /training-material/. If that is available we
// can opt in the fancy click-to-run features. Otherwise we fallback to
// displaying the real GTN.
fetch("/training-material/")
.then((response) => {
if (!response.ok) {
url = "https://training.galaxyproject.org/training-material/?utm_source=webhook&utm_medium=noproxy&utm_campaign=gxy";
message = `
function restoreLocation() {}
function persistLocation() {
// Don't save every scroll event.
var time = new Date().getTime();
if (time - lastUpdate < 1000) {
return;
}
lastUpdate = time;
window.localStorage.setItem("gtn-in-galaxy", `${getIframeScroll()} ${getIframeUrl()}`);
}
function addIframe() {
let url, message, onloadscroll;
gtnWebhookLoaded = true;
let storedData = false;
let safe = false;
// Test for the presence of /training-material/. If that is available we
// can opt in the fancy click-to-run features. Otherwise we fallback to
// displaying the real GTN.
fetch("/training-material/")
.then((response) => {
if (!response.ok) {
url =
"https://training.galaxyproject.org/training-material/?utm_source=webhook&utm_medium=noproxy&utm_campaign=gxy";
message = `
<span>
<a href="https://docs.galaxyproject.org/en/master/admin/special_topics/gtn.html">Click to run</a> unavailable.
</span>`;
} else {
safe = true;
var storedLocation = window.localStorage.getItem("gtn-in-galaxy");
if (
storedLocation !== null &&
storedLocation.split(" ")[1] !== undefined &&
storedLocation.split(" ")[1].startsWith("/training-material/")
) {
onloadscroll = storedLocation.split(" ")[0];
url = storedLocation.split(" ")[1];
} else {
url = "/training-material/?utm_source=webhook&utm_medium=proxy&utm_campaign=gxy";
safe = true;
var storedLocation = window.localStorage.getItem("gtn-in-galaxy");
if (
storedLocation !== null &&
storedLocation.split(" ")[1] !== undefined &&
storedLocation.split(" ")[1].startsWith("/training-material/")
) {
onloadscroll = storedLocation.split(" ")[0];
url = storedLocation.split(" ")[1];
} else {
url = "/training-material/?utm_source=webhook&utm_medium=proxy&utm_campaign=gxy";
}
message = "";
}
message = "";
}
})
.then(() => {
document.querySelector("body.full-content").insertAdjacentHTML(
"afterbegin",
`
})
.then(() => {
document.querySelector("body.full-content").insertAdjacentHTML(
"afterbegin",
`
<div id="gtn-container">
<div id="gtn-screen-overlay"></div>
<div id="gtn-screen">
@@ -88,100 +93,101 @@ function addIframe() {
</div>
</div>
</div>`
);
);
// Clicking outside of GTN closes it
document.getElementById("gtn-screen").addEventListener("click", () => {
removeOverlay();
});
// Only setup the listener if it won't crash things.
if (safe) {
// Listen to the scroll position
document.getElementById("gtn-embed").contentWindow.addEventListener("scroll", () => {
persistLocation();
});
}
// Depends on the iframe being present
document.getElementById("gtn-embed").addEventListener("load", () => {
// Save our current location when possible
if (onloadscroll !== undefined) {
document.getElementById("gtn-embed").contentWindow.scrollTo(0, parseInt(onloadscroll));
onloadscroll = undefined;
}
if (safe) {
persistLocation();
}
var gtn_tools = $("#gtn-embed").contents().find("span[data-tool]");
// Buttonify
gtn_tools.addClass("galaxy-proxy-active");
gtn_tools.click((e) => {
var target = e.target;
// Sometimes we get the i or the strong, not the parent.
if (e.target.tagName.toLowerCase() !== "span") {
target = e.target.parentElement;
}
tool_id = $(target).data("tool");
if (tool_id === "upload1" || tool_id === "upload") {
document.getElementById("tool-panel-upload-button").click();
} else {
Galaxy.router.push(`?tool_id=${tool_id}`);
}
// Clicking outside of GTN closes it
document.getElementById("gtn-screen").addEventListener("click", () => {
removeOverlay();
});
});
});
}
/* The masthead icon may not exist yet when this webhook executes; we need this to wait for that to happen.
* elementReady function from gist:
* https://gist.github.com/jwilson8767/db379026efcbd932f64382db4b02853e
*/
function elementReady(selector) {
return new Promise((resolve, reject) => {
let el = document.querySelector(selector);
if (el) {
resolve(el);
}
new MutationObserver((mutationRecords, observer) => {
// Query for elements matching the specified selector
Array.from(document.querySelectorAll(selector)).forEach((element) => {
resolve(element);
//Once we have resolved we don't need the observer anymore.
observer.disconnect();
});
}).observe(document.documentElement, {
childList: true,
subtree: true,
});
});
}
elementReady("#gtn a").then((el) => {
// External stuff may also have attached a click handler here (vue-based masthead)
// replace with a clean copy of the node to remove all that cruft.
clean = el.cloneNode(true);
el.parentNode.replaceChild(clean, el);
clean.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
if (!gtnWebhookLoaded) {
addIframe();
} else {
showOverlay();
}
});
});
// Only setup the listener if it won't crash things.
if (safe) {
// Listen to the scroll position
document.getElementById("gtn-embed").contentWindow.addEventListener("scroll", () => {
persistLocation();
});
}
// Remove the overlay on escape button click
document.addEventListener("keydown", (e) => {
// Check for escape button - "27"
if (e.which === 27 || e.keyCode === 27) {
removeOverlay();
// Depends on the iframe being present
document.getElementById("gtn-embed").addEventListener("load", () => {
// Save our current location when possible
if (onloadscroll !== undefined) {
document.getElementById("gtn-embed").contentWindow.scrollTo(0, parseInt(onloadscroll));
onloadscroll = undefined;
}
if (safe) {
persistLocation();
}
var gtn_tools = $("#gtn-embed").contents().find("span[data-tool]");
// Buttonify
gtn_tools.addClass("galaxy-proxy-active");
gtn_tools.click((e) => {
var target = e.target;
// Sometimes we get the i or the strong, not the parent.
if (e.target.tagName.toLowerCase() !== "span") {
target = e.target.parentElement;
}
tool_id = $(target).data("tool");
if (tool_id === "upload1" || tool_id === "upload") {
document.getElementById("tool-panel-upload-button").click();
} else {
Galaxy.router.push(`?tool_id=${tool_id}`);
}
removeOverlay();
});
});
});
}
});
/* The masthead icon may not exist yet when this webhook executes; we need this to wait for that to happen.
* elementReady function from gist:
* https://gist.github.com/jwilson8767/db379026efcbd932f64382db4b02853e
*/
function elementReady(selector) {
return new Promise((resolve, reject) => {
let el = document.querySelector(selector);
if (el) {
resolve(el);
}
new MutationObserver((mutationRecords, observer) => {
// Query for elements matching the specified selector
Array.from(document.querySelectorAll(selector)).forEach((element) => {
resolve(element);
//Once we have resolved we don't need the observer anymore.
observer.disconnect();
});
}).observe(document.documentElement, {
childList: true,
subtree: true,
});
});
}
elementReady("#gtn a").then((el) => {
// External stuff may also have attached a click handler here (vue-based masthead)
// replace with a clean copy of the node to remove all that cruft.
clean = el.cloneNode(true);
el.parentNode.replaceChild(clean, el);
clean.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
if (!gtnWebhookLoaded) {
addIframe();
} else {
showOverlay();
}
});
});
// Remove the overlay on escape button click
document.addEventListener("keydown", (e) => {
// Check for escape button - "27"
if (e.which === 27 || e.keyCode === 27) {
removeOverlay();
}
});
})();
+91 -86
View File
@@ -1,80 +1,84 @@
function hideNewsOverlay() {
document.getElementById("news-container").style.visibility = "hidden";
}
function newsSeen() {
// When it's seen, remove fa, add far.
const newsIconSpan = document.querySelector("#news .fa-bell");
newsIconSpan.classList.remove("fa");
newsIconSpan.classList.add("far");
window.localStorage.setItem("galaxy-news-seen-release", Galaxy.config.version_major);
}
function newsUnseen() {
// When there is news, remove far, add fa for (default -- same as fas) solid style.
const newsIconSpan = document.querySelector("#news .fa-bell");
newsIconSpan.classList.remove("far");
newsIconSpan.classList.add("fa");
}
/* The masthead icon may not exist yet when this webhook executes; we need this to wait for that to happen.
* elementReady function from gist:
* https://gist.github.com/jwilson8767/db379026efcbd932f64382db4b02853e
*/
function elementReadyNews(selector) {
return new Promise((resolve, reject) => {
const el = document.querySelector(selector);
if (el) {
resolve(el);
(function () {
function hideNewsOverlay() {
const container = document.getElementById("news-container");
if (container) {
container.style.visibility = "hidden";
}
new MutationObserver((mutationRecords, observer) => {
// Query for elements matching the specified selector
Array.from(document.querySelectorAll(selector)).forEach((element) => {
resolve(element);
//Once we have resolved we don't need the observer anymore.
observer.disconnect();
}
function newsSeen() {
// When it's seen, remove fa, add far.
const newsIconSpan = document.querySelector("#news .fa-bell");
newsIconSpan.classList.remove("fa");
newsIconSpan.classList.add("far");
window.localStorage.setItem("galaxy-news-seen-release", Galaxy.config.version_major);
}
function newsUnseen() {
// When there is news, remove far, add fa for (default -- same as fas) solid style.
const newsIconSpan = document.querySelector("#news .fa-bell");
newsIconSpan.classList.remove("far");
newsIconSpan.classList.add("fa");
}
/* The masthead icon may not exist yet when this webhook executes; we need this to wait for that to happen.
* elementReady function from gist:
* https://gist.github.com/jwilson8767/db379026efcbd932f64382db4b02853e
*/
function elementReadyNews(selector) {
return new Promise((resolve, reject) => {
const el = document.querySelector(selector);
if (el) {
resolve(el);
}
new MutationObserver((mutationRecords, observer) => {
// Query for elements matching the specified selector
Array.from(document.querySelectorAll(selector)).forEach((element) => {
resolve(element);
//Once we have resolved we don't need the observer anymore.
observer.disconnect();
});
}).observe(document.documentElement, {
childList: true,
subtree: true,
});
}).observe(document.documentElement, {
childList: true,
subtree: true,
});
});
}
elementReadyNews("#news a").then((el) => {
// External stuff may also have attached a click handler here (vue-based masthead)
// replace with a clean copy of the node to remove all that cruft.
clean = el.cloneNode(true);
el.parentNode.replaceChild(clean, el);
let currentGalaxyVersion = Galaxy.config.version_major;
// TODO/@hexylena: By 21.01 we will have a proper solution for this. For
// now we'll hardcode the version users 'see'. @hexylena will remove this
// code when she writes the user-facing release notes, and then will file
// an issue for how we'll fix this properly.
if (currentGalaxyVersion == "22.01") {
currentGalaxyVersion = "21.09";
}
const releaseNotes = `https://docs.galaxyproject.org/en/latest/releases/${currentGalaxyVersion}_announce_user.html`;
const lastSeenVersion = window.localStorage.getItem("galaxy-news-seen-release");
// Check that they've seen the current version's release notes.
if (lastSeenVersion != currentGalaxyVersion) {
newsUnseen();
} else {
newsSeen();
}
elementReadyNews("#news a").then((el) => {
// External stuff may also have attached a click handler here (vue-based masthead)
// replace with a clean copy of the node to remove all that cruft.
clean = el.cloneNode(true);
el.parentNode.replaceChild(clean, el);
clean.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
let currentGalaxyVersion = Galaxy.config.version_major;
// If element doesn't exist, add it.
if (document.getElementById("news-container") == null) {
document.querySelector("body.full-content").insertAdjacentHTML(
"afterbegin",
`
// TODO/@hexylena: By 21.01 we will have a proper solution for this. For
// now we'll hardcode the version users 'see'. @hexylena will remove this
// code when she writes the user-facing release notes, and then will file
// an issue for how we'll fix this properly.
if (currentGalaxyVersion == "22.01") {
currentGalaxyVersion = "21.09";
}
const releaseNotes = `https://docs.galaxyproject.org/en/latest/releases/${currentGalaxyVersion}_announce_user.html`;
const lastSeenVersion = window.localStorage.getItem("galaxy-news-seen-release");
// Check that they've seen the current version's release notes.
if (lastSeenVersion != currentGalaxyVersion) {
newsUnseen();
} else {
newsSeen();
}
clean.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
// If element doesn't exist, add it.
if (document.getElementById("news-container") == null) {
document.querySelector("body.full-content").insertAdjacentHTML(
"afterbegin",
`
<div id="news-container" style="visibility: hidden">
<div id="news-screen-overlay"></div>
<div id="news-screen">
@@ -83,21 +87,22 @@ elementReadyNews("#news a").then((el) => {
</div>
</div>
</div>`
);
// Clicking outside of GTN closes it
document.getElementById("news-screen").addEventListener("click", () => {
hideNewsOverlay();
});
}
document.getElementById("news-container").style.visibility = "visible";
newsSeen();
);
// Clicking outside of GTN closes it
document.getElementById("news-screen").addEventListener("click", () => {
hideNewsOverlay();
});
}
document.getElementById("news-container").style.visibility = "visible";
newsSeen();
});
});
});
// Remove the overlay on escape button click
document.addEventListener("keydown", (e) => {
// Check for escape button - "27"
if (e.which === 27 || e.keyCode === 27) {
hideNewsOverlay();
}
});
// Remove the overlay on escape button click
document.addEventListener("keydown", (e) => {
// Check for escape button - "27"
if (e.which === 27 || e.keyCode === 27) {
hideNewsOverlay();
}
});
})();