From 7fcb2940929f3a911f769dc387eafa17382ba6ae Mon Sep 17 00:00:00 2001 From: Harm Manders Date: Fri, 1 Nov 2024 17:09:41 +0100 Subject: [PATCH 01/25] updated some packages --- package-lock.json | 84 +++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/package-lock.json b/package-lock.json index ab679598..89d6978f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3048,25 +3048,6 @@ "node": ">= 14" } }, - "node_modules/@octokit/request/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/@octokit/rest": { "version": "19.0.13", "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-19.0.13.tgz", @@ -3635,11 +3616,11 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.16", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz", - "integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==", + "version": "22.8.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.4.tgz", + "integrity": "sha512-SpNNxkftTJOPk0oN+y2bIqurEXHTA2AOZ3EJDDKeJ5VzkvvORSvmQXGQarcOzWV1ac7DCaPBEdMDxBsM+d8jWw==", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.8" } }, "node_modules/@types/parse-json": { @@ -12466,6 +12447,25 @@ "lower-case": "^1.1.1" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -18448,9 +18448,9 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", @@ -23364,16 +23364,6 @@ "is-plain-object": "^5.0.0", "node-fetch": "^2.6.7", "universal-user-agent": "^6.0.0" - }, - "dependencies": { - "node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "requires": { - "whatwg-url": "^5.0.0" - } - } } }, "@octokit/request-error": { @@ -23873,11 +23863,11 @@ "dev": true }, "@types/node": { - "version": "20.11.16", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz", - "integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==", + "version": "22.8.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.4.tgz", + "integrity": "sha512-SpNNxkftTJOPk0oN+y2bIqurEXHTA2AOZ3EJDDKeJ5VzkvvORSvmQXGQarcOzWV1ac7DCaPBEdMDxBsM+d8jWw==", "requires": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.8" } }, "@types/parse-json": { @@ -30926,6 +30916,14 @@ "lower-case": "^1.1.1" } }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, "node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -35647,9 +35645,9 @@ } }, "undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", From f33e4198f2b085c5bee04db359a5bf9be54b45b5 Mon Sep 17 00:00:00 2001 From: Harm Manders Date: Fri, 1 Nov 2024 17:10:38 +0100 Subject: [PATCH 02/25] Added Export CSV admin page with Export class --- src/router/routes.js | 18 +++ src/utils/exports/BaseDataExport.js | 51 ++++++++ src/utils/exports/SubscriptionDataExport.js | 123 ++++++++++++++++++++ src/utils/exports/UserDataExport.js | 38 ++++++ src/views/Admin/ExportCSV.vue | 68 +++++++++++ src/views/Admin/index.vue | 5 + 6 files changed, 303 insertions(+) create mode 100644 src/utils/exports/BaseDataExport.js create mode 100644 src/utils/exports/SubscriptionDataExport.js create mode 100644 src/utils/exports/UserDataExport.js create mode 100644 src/views/Admin/ExportCSV.vue diff --git a/src/router/routes.js b/src/router/routes.js index ef40b7ed..faa47953 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -1070,6 +1070,24 @@ const routes = [ }, ], }, + { + path: "export_csv", + component: { + render(c) { + return c("router-view"); + }, + }, + meta: { + title: "Analytics", + }, + children: [ + { + path: "", + name: "Export CSV ", + component: () => import("src/views/Admin/ExportCSV.vue"), + }, + ], + }, { path: "export", component: { diff --git a/src/utils/exports/BaseDataExport.js b/src/utils/exports/BaseDataExport.js new file mode 100644 index 00000000..255397b9 --- /dev/null +++ b/src/utils/exports/BaseDataExport.js @@ -0,0 +1,51 @@ +export default class BaseDataExport { + constructor() { + if (new.target == BaseDataExport) { + throw new Error("Cannot instantiate an abstract class."); + } + this.loading = false; + } + + startLoading() { + this.loading = false; + } + + stopLoading() { + this.loading = false; + } + + isLoading() { + return this.loading; + } + + // Abstract method to retrieve CSV rows + async getCSVRows() { + throw new Error("getCSVRows() must be implemented by subclasses."); + } + + // Method to export rows to a CSV file + exportToCSV(rows, filename = "dataExport.csv") { + if (!rows || rows.length === 0) { + console.error("No data available for CSV export."); + return; + } + + // Convert rows array to CSV format + const csvContent = rows.map((row) => row.join(",")).join("\n"); + + // Create a Blob with CSV content and make it downloadable + const blob = new Blob([csvContent], { type: "text/csv" }); + const url = URL.createObjectURL(blob); + + // Create a link element to trigger the download + const link = document.createElement("a"); + link.href = url; + link.setAttribute("download", filename); + document.body.appendChild(link); + link.click(); + + // Clean up and revoke the object URL + document.body.removeChild(link); + URL.revokeObjectURL(url); + } +} diff --git a/src/utils/exports/SubscriptionDataExport.js b/src/utils/exports/SubscriptionDataExport.js new file mode 100644 index 00000000..c437fd10 --- /dev/null +++ b/src/utils/exports/SubscriptionDataExport.js @@ -0,0 +1,123 @@ +import { db, functions } from "src/firebase"; +import { makeDate } from "../generalFunctions"; +import BaseDataExport from "./BaseDataExport"; + +export default class UserDataExport extends BaseDataExport { + constructor() { + super(); + } + + // Implementing the abstract getCSVRows method + async getCSVRows() { + this.startLoading(); + + try { + // Simulate data fetching + const rows = await this.getPatrons(); + return rows; + } catch (error) { + console.error("Error fetching user data for CSV export:", error); + throw error; + } finally { + this.stopLoading(); + } + } + + async getPatrons() { + try { + // const getUserInfo = functions.httpsCallable("updateUsersEndpoint"); + // console.log(await getUserInfo()); + // return; + const patreon_ref = db.ref("new_patrons"); + const payload = await patreon_ref.once("value"); + if (!payload.exists()) { + console.error("No patrons found"); + } + const patrons = Object.values(payload.val()); + + const csvHeader = [ + "Created", + "Subscribed", + "Legacy", + "Tier", + "Status", + "Time till subscription in days", + "Time till subscription in hours", + "Time till subscription in ms", + ]; + const csvRows = [csvHeader]; + + console.log("PATRONS", patrons); + csvRows += Promise.all( + patrons.map(async (patron) => { + const user_payload = await db + .ref("users") + .orderByChild("patreon_email") + .equalTo(patron.email) + .once("value"); + if (!user_payload.exists()) { + console.error("No corresponding user found for patron:", patron); + } + const user = user_payload.val(); + console.log(user); + + const created = patron.created ? new Date(patron.created) : new Date(2024, 4, 15); + const difference = new Date(patron.pledge_start) - created; + + const P = { + created: makeDate(created, true, true).replace(" at", ""), + subscribed: makeDate(patron.pledge_start, true, true).replace(" at", ""), + legacy: !patron.created, + tier: patron.tiers ? Object.keys(patron.tiers)[0] : null, + status: patron.status, + time_till_subscription_days: difference.min(0) + ? difference.min(0) / 1000 / 60 / 60 / 24 + : 0, + time_till_subscription_hours: difference.min(0) + ? difference.min(0) / 1000 / 60 / 60 + : 0, + time_till_subscription_ms: difference.min(0) || 0, + }; + return Object.values(P); + }) + ); + // for (let patron of patrons) { + // // Get user created date + // await db + // .ref("users") + // .orderByChild("patreon_email") + // .equalTo(patron.email) + // .once("value", (result) => { + // const user = result.val() ? Object.values(result.val())[0] : null; + // if (user) { + // patron.created = user.created; + // } + // }); + + // const created = patron.created ? new Date(patron.created) : new Date(2024, 4, 15); + // const difference = new Date(patron.pledge_start) - created; + + // patron = { + // created: makeDate(created, true, true).replace(" at", ""), + // subscribed: makeDate(patron.pledge_start, true, true).replace(" at", ""), + // legacy: !patron.created, + // tier: patron.tiers ? Object.keys(patron.tiers)[0] : null, + // status: patron.status, + // time_till_subscription_days: difference.min(0) + // ? difference.min(0) / 1000 / 60 / 60 / 24 + // : 0, + // time_till_subscription_hours: difference.min(0) ? difference.min(0) / 1000 / 60 / 60 : 0, + // time_till_subscription_ms: difference.min(0) || 0, + // }; + // console.log(patron); + + // csvRows.push(Object.values(patron)); + // console.log(csvRows); + // } + console.log("ROWS", csvRows); + return csvRows; + } catch (e) { + console.error(e); + } + } +} diff --git a/src/utils/exports/UserDataExport.js b/src/utils/exports/UserDataExport.js new file mode 100644 index 00000000..22f2ffb6 --- /dev/null +++ b/src/utils/exports/UserDataExport.js @@ -0,0 +1,38 @@ +import BaseDataExport from "./BaseDataExport"; + +export default class UserDataExport extends BaseDataExport { + constructor() { + super(); + } + + // Implementing the abstract getCSVRows method + async getCSVRows() { + this.startLoading(); + + try { + // Simulate data fetching + const rows = await this.fetchUserData(); + return rows; + } catch (error) { + console.error("Error fetching user data for CSV export:", error); + throw error; + } finally { + this.stopLoading(); + } + } + + // Method to simulate data fetching + async fetchUserData() { + // Simulate network request delay + return new Promise((resolve) => { + setTimeout(() => { + resolve([ + ["Username", "Email", "Sign-up Date"], + ["Alice", "alice@example.com", "2021-01-15"], + ["Bob", "bob@example.com", "2021-03-22"], + ["Charlie", "charlie@example.com", "2021-05-18"], + ]); + }, 1000); + }); + } +} diff --git a/src/views/Admin/ExportCSV.vue b/src/views/Admin/ExportCSV.vue new file mode 100644 index 00000000..9856da90 --- /dev/null +++ b/src/views/Admin/ExportCSV.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/src/views/Admin/index.vue b/src/views/Admin/index.vue index af111687..8d923a08 100644 --- a/src/views/Admin/index.vue +++ b/src/views/Admin/index.vue @@ -51,6 +51,11 @@ export default { url: "subscriptions", icon: "fab fa-patreon", }, + data_exports: { + name: "Data Exports", + url: "export_csv", + icon: "fas fa-file-spreadsheet", + }, // prerender: { // name: "Generate prerender paths JSON", // url: "prerender", From 4d44d1783fec35ac8e80bdba435c8a4ecea043a8 Mon Sep 17 00:00:00 2001 From: Harm Manders Date: Fri, 1 Nov 2024 17:24:08 +0100 Subject: [PATCH 03/25] call userinfo function --- src/firebase.js | 13 +++++++----- src/utils/exports/SubscriptionDataExport.js | 3 --- src/utils/exports/UserDataExport.js | 23 ++++++++++----------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/firebase.js b/src/firebase.js index 1f733794..0cd87a1e 100644 --- a/src/firebase.js +++ b/src/firebase.js @@ -1,7 +1,8 @@ -import firebase from 'firebase/app'; -import 'firebase/auth'; -import 'firebase/database'; -import 'firebase/storage'; +import firebase from "firebase/app"; +import "firebase/functions"; +import "firebase/auth"; +import "firebase/database"; +import "firebase/storage"; const config = { apiKey: process.env.VUE_APP_FIREBASE_API_KEY, @@ -20,4 +21,6 @@ const auth = firebase.auth(); const db = firebase.database(); const storage = firebase.storage(); -export { firebase, auth, db, storage }; +const functions = firebase.functions(); + +export { firebase, auth, db, storage, functions }; diff --git a/src/utils/exports/SubscriptionDataExport.js b/src/utils/exports/SubscriptionDataExport.js index c437fd10..0572b630 100644 --- a/src/utils/exports/SubscriptionDataExport.js +++ b/src/utils/exports/SubscriptionDataExport.js @@ -25,9 +25,6 @@ export default class UserDataExport extends BaseDataExport { async getPatrons() { try { - // const getUserInfo = functions.httpsCallable("updateUsersEndpoint"); - // console.log(await getUserInfo()); - // return; const patreon_ref = db.ref("new_patrons"); const payload = await patreon_ref.once("value"); if (!payload.exists()) { diff --git a/src/utils/exports/UserDataExport.js b/src/utils/exports/UserDataExport.js index 22f2ffb6..7464bfd1 100644 --- a/src/utils/exports/UserDataExport.js +++ b/src/utils/exports/UserDataExport.js @@ -1,3 +1,5 @@ +import { db, functions } from "src/firebase"; + import BaseDataExport from "./BaseDataExport"; export default class UserDataExport extends BaseDataExport { @@ -21,18 +23,15 @@ export default class UserDataExport extends BaseDataExport { } } - // Method to simulate data fetching async fetchUserData() { - // Simulate network request delay - return new Promise((resolve) => { - setTimeout(() => { - resolve([ - ["Username", "Email", "Sign-up Date"], - ["Alice", "alice@example.com", "2021-01-15"], - ["Bob", "bob@example.com", "2021-03-22"], - ["Charlie", "charlie@example.com", "2021-05-18"], - ]); - }, 1000); - }); + try { + const getUserInfo = functions.httpsCallable("updateUsersEndpoint"); + console.log("Functions", getUserInfo); + const user_info = await getUserInfo(); + console.log("User info", user_info); + return [[JSON.stringify(user_info)]]; + } catch (e) { + console.error(e); + } } } From 45f25e6d618bc763050dbdf3fb10c06297126d23 Mon Sep 17 00:00:00 2001 From: Harm Manders Date: Sat, 2 Nov 2024 16:11:42 +0100 Subject: [PATCH 04/25] Change to let --- src/utils/exports/SubscriptionDataExport.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/exports/SubscriptionDataExport.js b/src/utils/exports/SubscriptionDataExport.js index 0572b630..8513868f 100644 --- a/src/utils/exports/SubscriptionDataExport.js +++ b/src/utils/exports/SubscriptionDataExport.js @@ -42,7 +42,7 @@ export default class UserDataExport extends BaseDataExport { "Time till subscription in hours", "Time till subscription in ms", ]; - const csvRows = [csvHeader]; + let csvRows = [csvHeader]; console.log("PATRONS", patrons); csvRows += Promise.all( From 9827f9aceee6b7462c95f642a6ddacc817ecc2bc Mon Sep 17 00:00:00 2001 From: Keyzer Date: Mon, 4 Nov 2024 20:21:35 +0100 Subject: [PATCH 05/25] Improved color labels --- src/components/combat/TargetAvatar.vue | 24 +-- src/components/combat/TargetItem.vue | 2 +- src/components/combat/Targeted.vue | 7 +- .../drawers/editEncounter/EditEntity.vue | 8 +- src/components/encounters/Overview.vue | 8 +- src/components/trackCampaign/live/Avatar.vue | 145 +++++++++--------- .../trackCampaign/live/Initiative.vue | 12 +- src/css/styles.scss | 11 +- 8 files changed, 122 insertions(+), 95 deletions(-) diff --git a/src/components/combat/TargetAvatar.vue b/src/components/combat/TargetAvatar.vue index ba1911ca..7b860a4b 100644 --- a/src/components/combat/TargetAvatar.vue +++ b/src/components/combat/TargetAvatar.vue @@ -12,25 +12,35 @@ ? 'url(' + entity.img + ')' : '', 'border-color': entity.color_label ? entity.color_label : ``, + 'background-color': entity.color_label ? entity.color_label : ``, color: entity.color_label ? entity.color_label : ``, }" > - + @@ -49,11 +59,6 @@ export default { default: true, }, }, - data() { - return {}; - }, - methods: {}, - mounted() {}, }; @@ -62,5 +67,6 @@ export default { background-position: center top; background-repeat: no-repeat; background-size: cover; + border-width: 5px; } diff --git a/src/components/combat/TargetItem.vue b/src/components/combat/TargetItem.vue index 8fa3e4b7..82126de8 100644 --- a/src/components/combat/TargetItem.vue +++ b/src/components/combat/TargetItem.vue @@ -91,7 +91,7 @@ -
+
@@ -123,7 +123,7 @@ ? { encounter_id: encounterId, entity_key: key, - } + } : null " > @@ -260,7 +260,7 @@ export default { return this.targeted.length > 1 ? this.options.filter((opt) => { return !["transform"].includes(opt.option); - }) + }) : this.options; }, }, @@ -471,6 +471,7 @@ export default { border-radius: $border-radius-small; border: none; cursor: pointer; + text-align: center; &:hover { background: $neutral-4; diff --git a/src/components/drawers/editEncounter/EditEntity.vue b/src/components/drawers/editEncounter/EditEntity.vue index b8ccdbe3..a65d173a 100644 --- a/src/components/drawers/editEncounter/EditEntity.vue +++ b/src/components/drawers/editEncounter/EditEntity.vue @@ -32,10 +32,16 @@ :style="{ backgroundImage: 'url(\'' + npc.avatar + '\')', 'border-color': npc.color_label ? npc.color_label : ``, + 'background-color': npc.color_label ? npc.color_label : ``, color: npc.color_label ? npc.color_label : ``, }" > -