From 449ead5575d6a7971b7a4acc316f847d11796c2a Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Thu, 2 Mar 2023 18:50:37 +0100 Subject: [PATCH] Integrate GA4 code directly with `book.js` (#470) * Integrate GA4 code directly with `book.js` The main advantage of this is that it simplifies the setup since we can avoid the monkey-patching we did before. A secondary advantage is that it should make things a little faster since we avoid a request to the server on every page load. * Remove unreachable return * Watch all of `third_party` It just occurred to me that we want to refresh the page in `mdbook serve` when anything changes in `third_party`. --- book.toml | 4 +-- ga4.js | 72 -------------------------------------- third_party/mdbook/book.js | 29 ++++++++++++++- 3 files changed, 30 insertions(+), 75 deletions(-) delete mode 100644 ga4.js diff --git a/book.toml b/book.toml index 1121766e..b3e81ac6 100644 --- a/book.toml +++ b/book.toml @@ -9,7 +9,7 @@ title = "Comprehensive Rust 🦀" edition = "2021" [build] -extra-watch-dirs = ["po"] +extra-watch-dirs = ["po", "third_party"] [preprocessor.gettext] after = ["links"] @@ -28,7 +28,7 @@ class = "bob" [output.html] curly-quotes = true -additional-js = ["ga4.js", "speaker-notes.js"] +additional-js = ["speaker-notes.js"] additional-css = ["svgbob.css", "speaker-notes.css", "language-picker.css"] site-url = "/comprehensive-rust/" git-repository-url = "https://github.com/google/comprehensive-rust" diff --git a/ga4.js b/ga4.js deleted file mode 100644 index 4f7dd63a..00000000 --- a/ga4.js +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Look through all Playgrounds on the page to determine if the code snippet -// matches one of the. If the code is different from all Playgrounds, we -// conclude that the user modified the Playground before submitting it. -function isPlaygroundCodeModified(code) { - // It sounds expensive to look through every Playground, but there are - // normally at most two Playground instances on a page. - let playgrounds = Array.from(document.querySelectorAll(".playground")); - return playgrounds.every(playground => { - let code_block = playground.querySelector("code"); - if (window.ace && code_block.classList.contains("editable")) { - let editor = window.ace.edit(code_block); - return code != editor.originalCode; - } else { - return code != code_block.textContent; - } - }); -} - -// Monkey-patch the window.fetch function so we can track the Playground -// executions. -const playgroundUrl = 'https://play.rust-lang.org/evaluate.json'; -const { fetch: originalFetch } = window; -window.fetch = async (...args) => { - let [resource, config ] = args; - if (resource != playgroundUrl) { - return originalFetch(resource, config); - } - - const startTime = window.performance.now(); - let endTime, errorMessage; - try { - // The fetch_with_timeout function from book.js defaults to a 15000 ms - // timeout. We use a slightly shorter timeout so that we can catch and log - // the error. - config.signal = AbortSignal.timeout(14500); - let response = await originalFetch(resource, config); - payload = await response.json(); - errorMessage = (payload.error == null) ? null : 'compilation_error'; - // Return object compatible with the unpackign done in book.js. - return {'json': () => payload}; - } catch (error) { - // fetch seems to always return AbortError, despite the example on - // https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout. - if (error.name == 'AbortError' || error.name == 'TimeoutError') { - error = new Error('timeout'); - } - errorMessage = error.message; - throw error; - } finally { - endTime = window.performance.now(); - let code = JSON.parse(config.body).code; - gtag("event", "playground", { - "modified": isPlaygroundCodeModified(code), - "error": errorMessage, - "latency": (endTime - startTime) / 1000, - }); - } -}; diff --git a/third_party/mdbook/book.js b/third_party/mdbook/book.js index 11099d9d..a3384d98 100644 --- a/third_party/mdbook/book.js +++ b/third_party/mdbook/book.js @@ -3,6 +3,16 @@ // Fix back button cache problem window.onunload = function () { }; +function isPlaygroundModified(playground) { + let code_block = playground.querySelector("code"); + if (window.ace && code_block.classList.contains("editable")) { + let editor = window.ace.edit(code_block); + return editor.getValue() != editor.originalCode; + } else { + return false; + } +} + // Global variable, shared between modules function playground_text(playground, hidden = true) { let code_block = playground.querySelector("code"); @@ -129,6 +139,8 @@ function playground_text(playground, hidden = true) { result_block.innerText = "Running..."; + const playgroundModified = isPlaygroundModified(code_block); + const startTime = window.performance.now(); fetch_with_timeout("https://play.rust-lang.org/evaluate.json", { headers: { 'Content-Type': "application/json", @@ -139,6 +151,13 @@ function playground_text(playground, hidden = true) { }) .then(response => response.json()) .then(response => { + const endTime = window.performance.now(); + gtag("event", "playground", { + "modified": playgroundModified, + "error": (response.error == null) ? null : 'compilation_error', + "latency": (endTime - startTime) / 1000, + }); + if (response.result.trim() === '') { result_block.innerText = "No output"; result_block.classList.add("result-no-output"); @@ -147,7 +166,15 @@ function playground_text(playground, hidden = true) { result_block.classList.remove("result-no-output"); } }) - .catch(error => result_block.innerText = "Playground Communication: " + error.message); + .catch(error => { + const endTime = window.performance.now(); + gtag("event", "playground", { + "modified": playgroundModified, + "error": error.message, + "latency": (endTime - startTime) / 1000, + }); + result_block.innerText = "Playground Communication: " + error.message + }); } // Syntax highlighting Configuration