From 476486d04091f35fd5d7aaee401b9057cc3661bf Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Fri, 10 Jan 2025 07:27:42 +0530 Subject: [PATCH 1/4] Add throttled ConfigLoader to cache try_examples config --- jupyterlite_sphinx/jupyterlite_sphinx.js | 137 ++++++++++++++++------- 1 file changed, 97 insertions(+), 40 deletions(-) diff --git a/jupyterlite_sphinx/jupyterlite_sphinx.js b/jupyterlite_sphinx/jupyterlite_sphinx.js index 4ae8c96..d1da0f7 100644 --- a/jupyterlite_sphinx/jupyterlite_sphinx.js +++ b/jupyterlite_sphinx/jupyterlite_sphinx.js @@ -149,55 +149,112 @@ var tryExamplesGlobalMinHeight = 0; */ var tryExamplesConfigLoaded = false; -window.loadTryExamplesConfig = async (configFilePath) => { - if (tryExamplesConfigLoaded) { - return; - } - try { - // Add a timestamp as query parameter to ensure a cached version of the - // file is not used. - const timestamp = new Date().getTime(); - const configFileUrl = `${configFilePath}?cb=${timestamp}`; - const currentPageUrl = window.location.pathname; - - const response = await fetch(configFileUrl); - if (!response.ok) { - if (response.status === 404) { - // Try examples ignore file is not present. - console.log("Optional try_examples config file not found."); - return; - } - throw new Error(`Error fetching ${configFilePath}`); +// A config loader with imprved error handling + request deduplication +const ConfigLoader = (() => { + // setting a private state for managing requests and errors + let configLoadPromise = null; + let lastErrorTimestamp = 0; + const ERROR_THROTTLE_MS = 5000; // error messages at most every 5 seconds + const failedRequestsCache = new Set(); + + const shouldShowError = () => { + const now = Date.now(); + if (now - lastErrorTimestamp > ERROR_THROTTLE_MS) { + lastErrorTimestamp = now; + return true; } + return false; + }; - const data = await response.json(); - if (!data) { + const logError = (message) => { + if (shouldShowError()) { + console.log(message); + } + }; + + const loadConfig = async (configFilePath) => { + if (tryExamplesConfigLoaded) { return; } - // Set minimum iframe height based on value in config file - if (data.global_min_height) { - tryExamplesGlobalMinHeight = parseInt(data.global_min_height); + if (failedRequestsCache.has(configFilePath)) { + return; } - // Disable interactive examples if file matches one of the ignore patterns - // by hiding try_examples_buttons. - Patterns = data.ignore_patterns; - for (let pattern of Patterns) { - let regex = new RegExp(pattern); - if (regex.test(currentPageUrl)) { - var buttons = document.getElementsByClassName("try_examples_button"); - for (var i = 0; i < buttons.length; i++) { - buttons[i].classList.add("hidden"); + // Return the existing promise if the request is in progress, as we + // don't want to make multiple requests for the same file. This + // can happen if there are several try_examples directives on the + // same page. + if (configLoadPromise) { + return configLoadPromise; + } + + configLoadPromise = (async () => { + try { + // Add a timestamp as query parameter to ensure a cached version of the + // file is not used. + const timestamp = new Date().getTime(); + const configFileUrl = `${configFilePath}?cb=${timestamp}`; + const currentPageUrl = window.location.pathname; + + const response = await fetch(configFileUrl); + if (!response.ok) { + if (response.status === 404) { + failedRequestsCache.add(configFilePath); + logError("Optional try_examples config file not found."); + return; + } + throw new Error(`Error fetching ${configFilePath}`); + } + + const data = await response.json(); + if (!data) { + return; } - break; + + // Set minimum iframe height based on value in config file + if (data.global_min_height) { + tryExamplesGlobalMinHeight = parseInt(data.global_min_height); + } + + // Disable interactive examples if file matches one of the ignore patterns + // by hiding try_examples_buttons. + Patterns = data.ignore_patterns; + for (let pattern of Patterns) { + let regex = new RegExp(pattern); + if (regex.test(currentPageUrl)) { + var buttons = document.getElementsByClassName( + "try_examples_button", + ); + for (var i = 0; i < buttons.length; i++) { + buttons[i].classList.add("hidden"); + } + break; + } + } + } catch (error) { + console.error(error); + } finally { + tryExamplesConfigLoaded = true; + configLoadPromise = null; } - } - } catch (error) { - console.error(error); - } - tryExamplesConfigLoaded = true; -}; + })(); + + return configLoadPromise; + }; + + return { + loadConfig, + resetState: () => { + tryExamplesConfigLoaded = false; + configLoadPromise = null; + failedRequestsCache.clear(); + lastErrorTimestamp = 0; + }, + }; +})(); + +window.loadTryExamplesConfig = ConfigLoader.loadConfig; window.toggleTryExamplesButtons = () => { /* Toggle visibility of TryExamples buttons. For use in console for debug From af7f8cdb39270bcfc548fa0b88473296859a00b2 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Fri, 10 Jan 2025 07:30:02 +0530 Subject: [PATCH 2/4] Fix typo --- jupyterlite_sphinx/jupyterlite_sphinx.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jupyterlite_sphinx/jupyterlite_sphinx.js b/jupyterlite_sphinx/jupyterlite_sphinx.js index d1da0f7..0b43007 100644 --- a/jupyterlite_sphinx/jupyterlite_sphinx.js +++ b/jupyterlite_sphinx/jupyterlite_sphinx.js @@ -149,7 +149,7 @@ var tryExamplesGlobalMinHeight = 0; */ var tryExamplesConfigLoaded = false; -// A config loader with imprved error handling + request deduplication +// A config loader with improved error handling + request deduplication const ConfigLoader = (() => { // setting a private state for managing requests and errors let configLoadPromise = null; From fcfd71c098e69b1d67a98cbfa8bee763008315a3 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Jan 2025 19:09:27 +0530 Subject: [PATCH 3/4] Remove throttling and simplify caching --- jupyterlite_sphinx/jupyterlite_sphinx.js | 31 +++--------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/jupyterlite_sphinx/jupyterlite_sphinx.js b/jupyterlite_sphinx/jupyterlite_sphinx.js index 0b43007..1d5315b 100644 --- a/jupyterlite_sphinx/jupyterlite_sphinx.js +++ b/jupyterlite_sphinx/jupyterlite_sphinx.js @@ -149,38 +149,15 @@ var tryExamplesGlobalMinHeight = 0; */ var tryExamplesConfigLoaded = false; -// A config loader with improved error handling + request deduplication +// A config loader with request deduplication + permanent caching const ConfigLoader = (() => { - // setting a private state for managing requests and errors let configLoadPromise = null; - let lastErrorTimestamp = 0; - const ERROR_THROTTLE_MS = 5000; // error messages at most every 5 seconds - const failedRequestsCache = new Set(); - - const shouldShowError = () => { - const now = Date.now(); - if (now - lastErrorTimestamp > ERROR_THROTTLE_MS) { - lastErrorTimestamp = now; - return true; - } - return false; - }; - - const logError = (message) => { - if (shouldShowError()) { - console.log(message); - } - }; const loadConfig = async (configFilePath) => { if (tryExamplesConfigLoaded) { return; } - if (failedRequestsCache.has(configFilePath)) { - return; - } - // Return the existing promise if the request is in progress, as we // don't want to make multiple requests for the same file. This // can happen if there are several try_examples directives on the @@ -200,8 +177,7 @@ const ConfigLoader = (() => { const response = await fetch(configFileUrl); if (!response.ok) { if (response.status === 404) { - failedRequestsCache.add(configFilePath); - logError("Optional try_examples config file not found."); + console.log("Optional try_examples config file not found."); return; } throw new Error(`Error fetching ${configFilePath}`); @@ -245,11 +221,10 @@ const ConfigLoader = (() => { return { loadConfig, + // for testing/debugging only, could be removed resetState: () => { tryExamplesConfigLoaded = false; configLoadPromise = null; - failedRequestsCache.clear(); - lastErrorTimestamp = 0; }, }; })(); From f4944bada11f36295740bf9725f0804068fec39c Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Jan 2025 19:12:38 +0530 Subject: [PATCH 4/4] Add comment, remove `configLoadPromise = null` --- jupyterlite_sphinx/jupyterlite_sphinx.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jupyterlite_sphinx/jupyterlite_sphinx.js b/jupyterlite_sphinx/jupyterlite_sphinx.js index 1d5315b..2304c86 100644 --- a/jupyterlite_sphinx/jupyterlite_sphinx.js +++ b/jupyterlite_sphinx/jupyterlite_sphinx.js @@ -166,6 +166,7 @@ const ConfigLoader = (() => { return configLoadPromise; } + // Create and cache the promise for the config request configLoadPromise = (async () => { try { // Add a timestamp as query parameter to ensure a cached version of the @@ -212,7 +213,6 @@ const ConfigLoader = (() => { console.error(error); } finally { tryExamplesConfigLoaded = true; - configLoadPromise = null; } })();