From a2176e74ec7c6243bc55d0bd5101c4ccb55d801d Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Tue, 17 Dec 2024 18:16:12 -0800 Subject: [PATCH] Add telemetry and back button to quiz. Add version watermark (#39) --- js/packages/repo-quest/package.json | 4 +- js/packages/repo-quest/src/index.tsx | 109 ++++++++++++++++++------ js/packages/repo-quest/styles/index.css | 28 +++++- js/packages/repo-quest/vite.config.ts | 9 +- js/pnpm-lock.yaml | 18 ++++ rs/crates/rq-core/src/git.rs | 49 +++++------ rs/crates/rq-core/src/quest.rs | 14 ++- 7 files changed, 176 insertions(+), 55 deletions(-) diff --git a/js/packages/repo-quest/package.json b/js/packages/repo-quest/package.json index 237e5f6..cf204aa 100644 --- a/js/packages/repo-quest/package.json +++ b/js/packages/repo-quest/package.json @@ -16,6 +16,7 @@ "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.1", "@wcrichto/quiz": "^0.3.8", + "highlight.js": "^11.11.0", "jsdom": "^25.0.0", "lodash": "^4.17.21", "marked": "^15.0.3", @@ -24,6 +25,7 @@ "normalize.css": "^8.0.1", "react": "^18.3.1", "react-dom": "^18.3.1", - "sass": "^1.78.0" + "sass": "^1.78.0", + "uuid": "^11.0.3" } } \ No newline at end of file diff --git a/js/packages/repo-quest/src/index.tsx b/js/packages/repo-quest/src/index.tsx index 1a963f7..c0df5fa 100644 --- a/js/packages/repo-quest/src/index.tsx +++ b/js/packages/repo-quest/src/index.tsx @@ -7,6 +7,7 @@ import { observer } from "mobx-react"; import React, { useContext, useEffect, useMemo, useRef, useState } from "react"; import { createPortal } from "react-dom"; import ReactDOM from "react-dom/client"; +import * as uuid from "uuid"; import guideMd from "../../../../GUIDE.md?raw"; import { @@ -21,6 +22,48 @@ import { commands } from "./bindings/backend"; +declare global { + var VERSION: string; + var COMMIT_HASH: string; + var TELEMETRY_URL: string; +} + +function getSessionId() { + const SESSION_STORAGE_KEY = "__repo_quest_telemetry_session"; + if (localStorage.getItem(SESSION_STORAGE_KEY) === null) { + localStorage.setItem(SESSION_STORAGE_KEY, uuid.v4()); + } + return localStorage.getItem(SESSION_STORAGE_KEY)!; +} + +class Telemetry { + private sessionId: string; + + constructor() { + this.sessionId = getSessionId(); + } + + // biome-ignore lint/suspicious/noExplicitAny: payload can be anything + log(_endpoint: string, payload: any) { + let log = { + sessionId: this.sessionId, + commitHash: COMMIT_HASH, + version: VERSION, + timestamp: new Date().getTime(), + payload + }; + + let fullUrl = `${TELEMETRY_URL}/rq_answers`; + fetch(fullUrl, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(log) + }); + } +} + let useWindowListener = ( event: K, listener: (this: Window, ev: WindowEventMap[K]) => void @@ -318,18 +361,28 @@ let NewQuest = () => { ); }; -let QuizPage: React.FC<{ quest: QuestConfig }> = ({ quest }) => { +let QuizPage: React.FC<{ quest: QuestConfig; goBack: () => void }> = ({ + quest, + goBack +}) => { let quiz = quest.final as /* biome-ignore lint/suspicious/noExplicitAny: backend guarantees that this satisfies Quiz */ any as Quiz; return ( - +
+
+ +
+ +
); }; @@ -337,8 +390,6 @@ let QuestView: React.FC<{ quest: QuestConfig; initialState: StateDescriptor; }> = ({ quest, initialState }) => { - console.debug(quest); - let loader = useContext(Loader.context)!; let [state, setState] = useState(initialState); let [showQuiz, setShowQuiz] = useState(false); @@ -346,16 +397,21 @@ let QuestView: React.FC<{ useEffect(() => setTitle(quest.title), [quest.title]); useEffect(() => { - events.stateEvent.listen(e => setState(e.payload)); + console.debug("QuestConfig", quest); + events.stateEvent.listen(e => { + if (!_.isEqual(e.payload, state)) setState(e.payload); + }); }, []); + console.debug("State", state); + let cur_stage = state && state.state.type === "Ongoing" ? state.state.stage : quest.stages.length - 1; if (showQuiz) { - return ; + return setShowQuiz(false)} />; } return ( @@ -365,6 +421,12 @@ let QuestView: React.FC<{ <>
Quest directory: {state.dir} +
{state.behind_origin && (
@@ -382,9 +444,13 @@ let QuestView: React.FC<{ /> ))} {state.state.type === "Completed" && quest.final && ( -
  • +
  • +
    + Quiz{" "} +
    - Quiz + Check your conceptual understanding of the material by + taking this quiz.
    -
    - -
    - {initialState.can_skip && (