From 870c90a2b822922b2d36b82f57f609db96c66eb3 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Tue, 31 Dec 2024 10:39:38 -0800 Subject: [PATCH 1/4] add frontend piwik tracking code --- src/ui-client/src/containers/App.tsx | 2 + src/ui-client/src/utils/envConfig.ts | 77 ++++++++++++++++++++++++++++ src/ui-client/src/utils/piwik.ts | 52 +++++++++++++++++++ 3 files changed, 131 insertions(+) create mode 100644 src/ui-client/src/utils/envConfig.ts create mode 100644 src/ui-client/src/utils/piwik.ts diff --git a/src/ui-client/src/containers/App.tsx b/src/ui-client/src/containers/App.tsx index 8316e120b..a1e308b0c 100644 --- a/src/ui-client/src/containers/App.tsx +++ b/src/ui-client/src/containers/App.tsx @@ -33,6 +33,7 @@ import DataImportContainer from '../containers/DataImport/DataImport'; import UserQuestionModal from './UserQuestionModal/UserQuestionModal'; import { SavedQueryMap } from '../models/Query'; import { sleep } from '../utils/Sleep'; +import { addMatomoTracking } from '../utils/piwik'; import NotificationModal from '../components/Modals/NotificationModal/NotificationModal'; import MaintainenceModal from '../components/Modals/MaintainenceModal/MaintainenceModal'; import './App.css'; @@ -74,6 +75,7 @@ class App extends React.Component { this.handleSessionTokenRefresh(); dispatch(getIdToken()); dispatch(refreshServerStateLoop()); + addMatomoTracking(); } public componentDidUpdate() { diff --git a/src/ui-client/src/utils/envConfig.ts b/src/ui-client/src/utils/envConfig.ts new file mode 100644 index 000000000..cc5329ef0 --- /dev/null +++ b/src/ui-client/src/utils/envConfig.ts @@ -0,0 +1,77 @@ +import Axios from "axios"; +import { getAuthConfig, getUserTokenAndContext } from "../services/authApi"; +interface EnvVar { + key: string; + value: string | object; +} + +export async function fetchEnvData(): Promise { + return new Promise((resolve) => { + // fetch data from appsettings.json and env.json + Promise.allSettled([ + getAuthConfig().then((config) => config), // general config settings + getAuthConfig().then((config) => getUserTokenAndContext(config)), // user auth specific settings + Axios.get("/env.json"), // env.json, generated via script to be populated with environment variables + ]) + .then((results) => { + let returnResults = {}; + if (results[0].value) { + returnResults = results[0].value; + } + if (results[1].value) { + returnResults = { + ...returnResults, + user: results[1].value + } + } + if (results[2].value && results[2].value.data) { + returnResults = { + ...returnResults, + ...results[2].value.data + } + } + resolve(returnResults); + }) + .catch((e) => resolve(null)); + }); +} +// data coming from appsettings && env.json +export async function getEnvData() { + const results = await fetchEnvData(); + if (window && results) { + window["appConfig"] = results; + console.table("Environment variables:",getEnvs()); + } + return results; +} +export function getEnv(key: string) { + //window application global variables + if (window && window["appConfig"] && window["appConfig"][key]) + return window["appConfig"][key]; + const envDefined = typeof process !== "undefined" && process.env; + //enviroment variables as defined by Node + if (envDefined && process.env[key]) return process.env[key]; + return ""; +} + +export function getEnvs(): EnvVar[] { + let arrEnvs: EnvVar[] = []; + const BLACK_LIST = ["SECRET", "KEY", "TOKEN", "CREDENTIALS"]; + if (window && window["appConfig"]) { + const keys = Object.keys(window["appConfig"]); + keys.forEach((key) => { + if (BLACK_LIST.indexOf(key.toUpperCase()) !== -1) return true; + arrEnvs.push({ key: key, value: window["appConfig"][key] }); + }); + } + const envDefined = typeof process !== "undefined" && process.env; + if (envDefined) { + const envKeys = Object.keys(process.env); + envKeys.forEach((key) => { + if (BLACK_LIST.indexOf(key.toUpperCase()) !== -1) return true; + arrEnvs.push({ key: key, value: process.env[key] }); + }); + } + console.log("Environment variables ", arrEnvs); + return arrEnvs; +} diff --git a/src/ui-client/src/utils/piwik.ts b/src/ui-client/src/utils/piwik.ts new file mode 100644 index 000000000..b806f7ed0 --- /dev/null +++ b/src/ui-client/src/utils/piwik.ts @@ -0,0 +1,52 @@ +import {getEnvData} from "./envConfig"; + +// get user Id from config, i.e. see getAuthConfig, getUserTokenAndContext from authAPI +export function getUserIdFromEnv(config) : number | string | null { + if (!config) return null; + if (config.user && config.user.name) return config.user.name; + if (config.profile) return config.profile; + if (config.fhirUser) return config.fhirUser; + if (config["preferred_username"]) return config["preferred_username"]; + return null; +} +// get PIWIK siteId from environment variable +export function getMatomoSiteIdFromEnv(config) : number | null{ + if (!config) return null; + if (config["REACT_APP_MATOMO_SITE_ID"]) return config["REACT_APP_MATOMO_SITE_ID"]; + if (config["MATOMO_SITE_ID"]) return config["MATOMO_SITE_ID"]; + if (config["REACT_APP_PIWIK_SITE_ID"]) return config["REACT_APP_PIWIK_SITE_ID"]; + if (config["PIWIK_SITE_ID"]) return config["PIWIK_SITE_ID"]; + return config["SITE_ID"]; +} +export async function addMatomoTracking() { + // already generated script, return + if (!window || document.querySelector("#matomoScript")) return; + const envData = await getEnvData(); + const userId = getUserIdFromEnv(envData); + console.log("PIWIK userId ", userId); + // no user Id return + if (!userId) return; + const siteId = getMatomoSiteIdFromEnv(envData); + console.log("PIWIK siteId ", siteId) + // no site Id return + if (!siteId) return; + // init global piwik tracking object + window._paq = []; + window._paq.push(["trackPageView"]); + window._paq.push(["enableLinkTracking"]); + window._paq.push(["setSiteId", siteId]); + window._paq.push(["setUserId", userId]); + + let u = "https://piwik.cirg.washington.edu/"; + window._paq.push(["setTrackerUrl", u + "matomo.php"]); + let d = document, + g = d.createElement("script"), + headElement = document.querySelector("head"); + g.type = "text/javascript"; + g.async = true; + g.defer = true; + g.setAttribute("src", u + "matomo.js"); + g.setAttribute("id", "matomoScript"); + headElement.appendChild(g); + } + \ No newline at end of file From 0208b865adc263e0f57d320d4e1c6835cf4f1100 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Tue, 31 Dec 2024 11:00:00 -0800 Subject: [PATCH 2/4] add comment, fix code --- src/ui-client/src/utils/piwik.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/ui-client/src/utils/piwik.ts b/src/ui-client/src/utils/piwik.ts index b806f7ed0..2d6955684 100644 --- a/src/ui-client/src/utils/piwik.ts +++ b/src/ui-client/src/utils/piwik.ts @@ -12,11 +12,14 @@ export function getUserIdFromEnv(config) : number | string | null { // get PIWIK siteId from environment variable export function getMatomoSiteIdFromEnv(config) : number | null{ if (!config) return null; - if (config["REACT_APP_MATOMO_SITE_ID"]) return config["REACT_APP_MATOMO_SITE_ID"]; - if (config["MATOMO_SITE_ID"]) return config["MATOMO_SITE_ID"]; - if (config["REACT_APP_PIWIK_SITE_ID"]) return config["REACT_APP_PIWIK_SITE_ID"]; - if (config["PIWIK_SITE_ID"]) return config["PIWIK_SITE_ID"]; - return config["SITE_ID"]; + const envs = config.client ? config.client: config; + // appsettings.json {client: .....} + // env.json + if (envs["REACT_APP_MATOMO_SITE_ID"]) return envs["REACT_APP_MATOMO_SITE_ID"]; + if (envs["MATOMO_SITE_ID"]) return envs["MATOMO_SITE_ID"]; + if (envs["REACT_APP_PIWIK_SITE_ID"]) return envs["REACT_APP_PIWIK_SITE_ID"]; + if (envs["PIWIK_SITE_ID"]) return envs["PIWIK_SITE_ID"]; + return envs["SITE_ID"]; } export async function addMatomoTracking() { // already generated script, return From 82990cbade47a54636fcba393ab4e6a18f3bc328 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Tue, 31 Dec 2024 11:09:06 -0800 Subject: [PATCH 3/4] remove extra line --- src/ui-client/src/utils/piwik.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ui-client/src/utils/piwik.ts b/src/ui-client/src/utils/piwik.ts index 2d6955684..80034860c 100644 --- a/src/ui-client/src/utils/piwik.ts +++ b/src/ui-client/src/utils/piwik.ts @@ -52,4 +52,3 @@ export async function addMatomoTracking() { g.setAttribute("id", "matomoScript"); headElement.appendChild(g); } - \ No newline at end of file From bc4bdaed64ce82796fbd40f0b60d21659ea76437 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Fri, 3 Jan 2025 11:25:33 -0800 Subject: [PATCH 4/4] add comments --- src/ui-client/src/utils/envConfig.ts | 1 + src/ui-client/src/utils/piwik.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ui-client/src/utils/envConfig.ts b/src/ui-client/src/utils/envConfig.ts index cc5329ef0..c98753049 100644 --- a/src/ui-client/src/utils/envConfig.ts +++ b/src/ui-client/src/utils/envConfig.ts @@ -21,6 +21,7 @@ export async function fetchEnvData(): Promise { if (results[1].value) { returnResults = { ...returnResults, + // user auth/context info will be under the `user` key user: results[1].value } } diff --git a/src/ui-client/src/utils/piwik.ts b/src/ui-client/src/utils/piwik.ts index 80034860c..06b9375c0 100644 --- a/src/ui-client/src/utils/piwik.ts +++ b/src/ui-client/src/utils/piwik.ts @@ -34,6 +34,7 @@ export async function addMatomoTracking() { // no site Id return if (!siteId) return; // init global piwik tracking object + // note this will only be executed if BOTH userId and siteId are present window._paq = []; window._paq.push(["trackPageView"]); window._paq.push(["enableLinkTracking"]);