From bd5238362dbfbdf0f4ed1aa4396d9c49cfbe85c5 Mon Sep 17 00:00:00 2001 From: Ross Hill Date: Sun, 25 Aug 2024 16:34:38 -0400 Subject: [PATCH] Add telemetry for certain events (#28) --- .github/workflows/deploy.yml | 3 + client/css/styles.scss | 6 ++ client/js/MandelbrotControls.ts | 18 +++++ client/js/api.ts | 21 +++++ client/package-lock.json | 132 ++++++++++++++++++++++++++++++-- client/package.json | 2 + client/webpack.config.js | 2 + 7 files changed, 179 insertions(+), 5 deletions(-) create mode 100644 client/js/api.ts diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 985b85a2..60f32d14 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -20,6 +20,9 @@ jobs: working-directory: ./client run: | npm run build + env: + SUPABASE_PROJECT_ID: ${{ secrets.SUPABASE_PROJECT_ID }} + SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} - name: Publish uses: netlify/actions/cli@master with: diff --git a/client/css/styles.scss b/client/css/styles.scss index e9c29b3d..ea208a71 100644 --- a/client/css/styles.scss +++ b/client/css/styles.scss @@ -145,6 +145,11 @@ datalist { > input[type="range"] { width: 100px; + + &::-webkit-slider-runnable-track { + background: #007aff; + border-radius: 48px; + } } > select.fullWidth { @@ -254,6 +259,7 @@ select { padding: 2px; background-color: white; color: black; + border-radius: 4px; &:not(:disabled)hover { opacity: 0.9; diff --git a/client/js/MandelbrotControls.ts b/client/js/MandelbrotControls.ts index c17af4b6..e702d9b1 100644 --- a/client/js/MandelbrotControls.ts +++ b/client/js/MandelbrotControls.ts @@ -2,6 +2,7 @@ import * as L from "leaflet"; import debounce from "lodash/debounce"; import type MandelbrotMap from "./MandelbrotMap"; import throttle from "lodash/throttle"; +import api from "./api"; type NumberInput = { id: "iterations" | "exponent" | "re" | "im" | "zoom"; @@ -125,6 +126,20 @@ class MandelbrotControls { }, 300); } + private async logEvent(eventName: "image_save" | "share") { + await api.client?.from("events").insert([ + { + event_name: eventName, + share_url: this.getShareUrl(), + re: String(this.map.config.re), + im: String(this.map.config.im), + zoom: this.map.config.zoom, + iterations: this.map.config.iterations, + session_id: api.sessionId, + }, + ]); + } + private handleSaveImageButton() { let isSavingImage = false; @@ -197,6 +212,8 @@ class MandelbrotControls { saveImageSubmitButton.setAttribute("disabled", "true"); closeModalButton.setAttribute("disabled", "true"); + this.logEvent("image_save"); + this.map .saveVisibleImage(width, height) .catch((error) => { @@ -261,6 +278,7 @@ class MandelbrotControls { navigator.clipboard.writeText(this.getShareUrl()).then(() => { alert("The URL for this view has been copied!"); }); + this.logEvent("share"); }; } diff --git a/client/js/api.ts b/client/js/api.ts new file mode 100644 index 00000000..8ed6ce75 --- /dev/null +++ b/client/js/api.ts @@ -0,0 +1,21 @@ +import { createClient } from "@supabase/supabase-js"; + +const client = + process.env.SUPABASE_PROJECT_ID && process.env.SUPABASE_ANON_KEY + ? createClient( + `https://${process.env.SUPABASE_PROJECT_ID}.supabase.co`, + process.env.SUPABASE_ANON_KEY, + ) + : null; + +let sessionId = `${Math.random()}|${Date.now()}`; +try { + sessionId = self.crypto.randomUUID() || sessionId; +} catch (e) { + console.warn("crypto.randomUUID() not available"); +} + +export default { + client, + sessionId, +}; diff --git a/client/package-lock.json b/client/package-lock.json index 22cf65c2..0248e463 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.1", "license": "MIT", "dependencies": { + "@supabase/supabase-js": "^2.45.2", "file-saver": "^2.0.5", "leaflet": "1.7.1", "lodash": "^4.17.21", @@ -25,6 +26,7 @@ "@typescript-eslint/parser": "^6.20.0", "@wasm-tool/wasm-pack-plugin": "^1.7.0", "css-loader": "^6.9.1", + "dotenv-webpack": "^8.1.0", "eslint": "^8.56.0", "file-loader": "^6.2.0", "front-matter": "^4.0.2", @@ -287,6 +289,73 @@ "node": ">= 8" } }, + "node_modules/@supabase/auth-js": { + "version": "2.64.4", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.64.4.tgz", + "integrity": "sha512-9ITagy4WP4FLl+mke1rchapOH0RQpf++DI+WSG2sO1OFOZ0rW3cwAM0nCrMOxu+Zw4vJ4zObc08uvQrXx590Tg==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.1.tgz", + "integrity": "sha512-8sZ2ibwHlf+WkHDUZJUXqqmPvWQ3UHN0W30behOJngVh/qHHekhJLCFbh0AjkE9/FqqXtf9eoVvmYgfCLk5tNA==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.15.8.tgz", + "integrity": "sha512-YunjXpoQjQ0a0/7vGAvGZA2dlMABXFdVI/8TuVKtlePxyT71sl6ERl6ay1fmIeZcqxiuFQuZw/LXUuStUG9bbg==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.10.2.tgz", + "integrity": "sha512-qyCQaNg90HmJstsvr2aJNxK2zgoKh9ZZA8oqb7UT2LCh3mj9zpa3Iwu167AuyNxsxrUE8eEJ2yH6wLCij4EApA==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14", + "@types/phoenix": "^1.5.4", + "@types/ws": "^8.5.10", + "ws": "^8.14.2" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.0.tgz", + "integrity": "sha512-iZenEdO6Mx9iTR6T7wC7sk6KKsoDPLq8rdu5VRy7+JiT1i8fnqfcOr6mfF2Eaqky9VQzhP8zZKQYjzozB65Rig==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.45.2", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.45.2.tgz", + "integrity": "sha512-kJKY3ISFusVKQWCP8Kqo20Ebxy2WLp6Ry/Suco0aQsPXH7bvn7clswsdhcfcH/5Tr0MYz/jcCjF0n/27SetiCw==", + "dependencies": { + "@supabase/auth-js": "2.64.4", + "@supabase/functions-js": "2.4.1", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.15.8", + "@supabase/realtime-js": "2.10.2", + "@supabase/storage-js": "2.7.0" + } + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -419,7 +488,6 @@ "version": "22.5.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.0.tgz", "integrity": "sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==", - "dev": true, "dependencies": { "undici-types": "~6.19.2" } @@ -433,6 +501,11 @@ "@types/node": "*" } }, + "node_modules/@types/phoenix": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.5.tgz", + "integrity": "sha512-xegpDuR+z0UqG9fwHqNoy3rI7JDlvaPh2TY47Fl80oq6g+hXT+c/LEuE43X48clZ6lOfANl5WrPur9fYO1RJ/w==" + }, "node_modules/@types/qs": { "version": "6.9.15", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", @@ -500,7 +573,6 @@ "version": "8.5.12", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -1824,6 +1896,39 @@ "tslib": "^2.0.3" } }, + "node_modules/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-defaults": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz", + "integrity": "sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg==", + "dev": true, + "dependencies": { + "dotenv": "^8.2.0" + } + }, + "node_modules/dotenv-webpack": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-8.1.0.tgz", + "integrity": "sha512-owK1JcsPkIobeqjVrk6h7jPED/W6ZpdFsMPR+5ursB7/SdgDyO+VzAU+szK8C8u3qUhtENyYnj8eyXMR5kkGag==", + "dev": true, + "dependencies": { + "dotenv-defaults": "^2.0.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "webpack": "^4 || ^5" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -5260,6 +5365,11 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -5430,8 +5540,7 @@ "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, "node_modules/unpipe": { "version": "1.0.0", @@ -5542,6 +5651,11 @@ "minimalistic-assert": "^1.0.0" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, "node_modules/webpack": { "version": "5.94.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", @@ -5898,6 +6012,15 @@ "node": ">=0.8.0" } }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5938,7 +6061,6 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "dev": true, "engines": { "node": ">=10.0.0" }, diff --git a/client/package.json b/client/package.json index 46c902a3..dc41f975 100644 --- a/client/package.json +++ b/client/package.json @@ -27,6 +27,7 @@ "@typescript-eslint/parser": "^6.20.0", "@wasm-tool/wasm-pack-plugin": "^1.7.0", "css-loader": "^6.9.1", + "dotenv-webpack": "^8.1.0", "eslint": "^8.56.0", "file-loader": "^6.2.0", "front-matter": "^4.0.2", @@ -43,6 +44,7 @@ "webpack-dev-server": "^4.15.1" }, "dependencies": { + "@supabase/supabase-js": "^2.45.2", "file-saver": "^2.0.5", "leaflet": "1.7.1", "lodash": "^4.17.21", diff --git a/client/webpack.config.js b/client/webpack.config.js index 3ed51a0f..d1fb0ac1 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -7,6 +7,7 @@ const { marked } = require("marked"); const frontMatter = require("front-matter"); const fs = require("fs"); const template = require("lodash/template"); +const Dotenv = require("dotenv-webpack"); const blogDir = "./blog"; for (const file of fs.readdirSync(blogDir)) { @@ -37,6 +38,7 @@ for (const file of fs.readdirSync(blogDir)) { const appConfig = { entry: "./js/main.ts", plugins: [ + new Dotenv(), new HtmlWebpackPlugin({ template: "html/index.html", root: path.resolve(__dirname, "."),