feat: Track collection events (#2256)
- Renames `inject_analytics` to `inject_extra` and updates docs - Manually tracks page views to enable passing custom props - Tracks copying collection share link and downloading a public collection --------- Co-authored-by: emma <hi@emma.cafe>
This commit is contained in:
parent
eb88e9f90c
commit
b36ed9f730
@ -62,9 +62,9 @@ spec:
|
||||
value: "{{ .Values.minio_local_bucket_name }}"
|
||||
{{- end }}
|
||||
|
||||
{{- if .Values.inject_analytics }}
|
||||
- name: INJECT_ANALYTICS
|
||||
value: {{ .Values.inject_analytics }}
|
||||
{{- if .Values.inject_extra }}
|
||||
- name: INJECT_EXTRA
|
||||
value: {{ .Values.inject_extra }}
|
||||
{{- end }}
|
||||
|
||||
resources:
|
||||
|
@ -451,9 +451,9 @@ ingress:
|
||||
|
||||
ingress_class: nginx
|
||||
|
||||
# Optional: Analytics injection script
|
||||
# This runs as a blocking script on the frontend, so usually you'll want to have it just add a single script tag to the page with the `defer` attribute.
|
||||
# inject_analytics: // your analytics injection script here
|
||||
# Optional: Front-end injected script
|
||||
# This runs as a blocking script on the frontend, so usually you'll want to have it just add a single script tag to the page with the `defer` attribute. Useful for things like analytics and bug tracking.
|
||||
# inject_extra: // your front-end injected script
|
||||
|
||||
|
||||
# Signing Options
|
||||
|
@ -5,7 +5,7 @@ rm /etc/nginx/conf.d/default.conf
|
||||
|
||||
if [ -z "$LOCAL_MINIO_HOST" ]; then
|
||||
echo "no local minio, clearing out minio route"
|
||||
echo "" > /etc/nginx/includes/minio.conf
|
||||
echo "" >/etc/nginx/includes/minio.conf
|
||||
else
|
||||
echo "local minio: replacing \$LOCAL_MINIO_HOST with \"$LOCAL_MINIO_HOST\", \$LOCAL_BUCKET with \"$LOCAL_BUCKET\""
|
||||
sed -i "s/\$LOCAL_MINIO_HOST/$LOCAL_MINIO_HOST/g" /etc/nginx/includes/minio.conf
|
||||
@ -13,15 +13,15 @@ else
|
||||
fi
|
||||
|
||||
# Add analytics script, if provided
|
||||
if [ -z "$INJECT_ANALYTICS" ]; then
|
||||
if [ -z "$INJECT_EXTRA" ]; then
|
||||
echo "analytics disabled, injecting blank script"
|
||||
echo "" > /usr/share/nginx/html/extra.js
|
||||
echo "" >/usr/share/nginx/html/extra.js
|
||||
else
|
||||
echo "analytics enabled, injecting script"
|
||||
echo "$INJECT_ANALYTICS" > /usr/share/nginx/html/extra.js
|
||||
echo "$INJECT_EXTRA" >/usr/share/nginx/html/extra.js
|
||||
fi
|
||||
|
||||
mkdir -p /etc/nginx/resolvers/
|
||||
echo resolver $(grep -oP '(?<=nameserver\s)[^\s]+' /etc/resolv.conf | awk '{ if ($1 ~ /:/) { printf "[" $1 "] "; } else { printf $1 " "; } }') valid=10s ipv6=off";" > /etc/nginx/resolvers/resolvers.conf
|
||||
echo resolver $(grep -oP '(?<=nameserver\s)[^\s]+' /etc/resolv.conf | awk '{ if ($1 ~ /:/) { printf "[" $1 "] "; } else { printf $1 " "; } }') valid=10s ipv6=off";" >/etc/nginx/resolvers/resolvers.conf
|
||||
|
||||
cat /etc/nginx/resolvers/resolvers.conf
|
||||
|
28
frontend/config/define.js
Normal file
28
frontend/config/define.js
Normal file
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Global constants to make available to build
|
||||
*
|
||||
* @TODO Consolidate webpack and web-test-runner esbuild configs
|
||||
*/
|
||||
const path = require("path");
|
||||
|
||||
const isDevServer = process.env.WEBPACK_SERVE;
|
||||
|
||||
const dotEnvPath = path.resolve(
|
||||
process.cwd(),
|
||||
`.env${isDevServer ? `.local` : ""}`,
|
||||
);
|
||||
require("dotenv").config({
|
||||
path: dotEnvPath,
|
||||
});
|
||||
|
||||
const WEBSOCKET_HOST =
|
||||
isDevServer && process.env.API_BASE_URL
|
||||
? new URL(process.env.API_BASE_URL).host
|
||||
: process.env.WEBSOCKET_HOST || "";
|
||||
|
||||
module.exports = {
|
||||
"window.process.env.WEBSOCKET_HOST": JSON.stringify(WEBSOCKET_HOST),
|
||||
"window.process.env.ANALYTICS_NAMESPACE": JSON.stringify(
|
||||
process.env.ANALYTICS_NAMESPACE || "",
|
||||
),
|
||||
};
|
@ -208,16 +208,22 @@ Browsertrix has the ability to cryptographically sign WACZ files with [Authsign]
|
||||
|
||||
You can enable sign-ups by setting `registration_enabled` to `"1"`. Once enabled, your users can register by visiting `/sign-up`.
|
||||
|
||||
## Analytics
|
||||
## Inject Extra JavaScript
|
||||
|
||||
You can add a script to inject any sort of analytics into the frontend by setting `inject_analytics` to the script. If present, it will be injected as a blocking script tag into every page — so we recommend you create the script tags that handle your analytics from within this script.
|
||||
You can add a script to inject analytics, bug reporting tools, etc. into the frontend by setting `inject_extra` to script contents of your choosing. If present, it will be injected as a blocking script tag that runs when the frontend web app is initialized.
|
||||
|
||||
For example, here's a script that adds Plausible Analytics tracking:
|
||||
For example, enabling analytics and tracking might look like this:
|
||||
|
||||
```ts
|
||||
const plausible = document.createElement("script");
|
||||
plausible.src = "https://plausible.io/js/script.js";
|
||||
plausible.defer = true;
|
||||
plausible.dataset.domain = "app.browsertrix.com";
|
||||
document.head.appendChild(plausible);
|
||||
```yaml
|
||||
inject_extra: >
|
||||
const analytics = document.createElement("script");
|
||||
analytics.src = "https://cdn.example.com/analytics.js";
|
||||
analytics.defer = true;
|
||||
|
||||
document.head.appendChild(analytics);
|
||||
|
||||
window.analytics = window.analytics
|
||||
|| function () { (window.analytics.q = window.analytics.q || []).push(arguments); };
|
||||
```
|
||||
|
||||
Note that the script will only run when the web app loads, i.e. the first time the app is loaded in the browser and on hard refresh. The script will not run again upon clicking a link in the web app. This shouldn't be an issue with most analytics libraries, which should listen for changes to [window history](https://developer.mozilla.org/en-US/docs/Web/API/History). If you have a custom script that needs to re-run when the frontend URL changes, you'll need to add an event listener for the [`popstate` event](https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event).
|
||||
|
@ -3,3 +3,5 @@ DOCS_URL=https://docs.browsertrix.com/
|
||||
E2E_USER_EMAIL=
|
||||
E2E_USER_PASSWORD=
|
||||
GLITCHTIP_DSN=
|
||||
INJECT_EXTRA=
|
||||
ANALYTICS_NAMESPACE=
|
||||
|
@ -20,11 +20,13 @@ import { SelectCollectionAccess } from "./select-collection-access";
|
||||
import { BtrixElement } from "@/classes/BtrixElement";
|
||||
import { ClipboardController } from "@/controllers/clipboard";
|
||||
import { RouteNamespace } from "@/routes";
|
||||
import { AnalyticsTrackEvent } from "@/trackEvents";
|
||||
import {
|
||||
CollectionAccess,
|
||||
type Collection,
|
||||
type PublicCollection,
|
||||
} from "@/types/collection";
|
||||
import { track } from "@/utils/analytics";
|
||||
|
||||
enum Tab {
|
||||
Link = "link",
|
||||
@ -113,6 +115,13 @@ export class ShareCollection extends BtrixElement {
|
||||
?disabled=${!this.shareLink}
|
||||
@click=${() => {
|
||||
void this.clipboardController.copy(this.shareLink);
|
||||
|
||||
track(AnalyticsTrackEvent.CopyShareCollectionLink, {
|
||||
org_slug: this.slug,
|
||||
collection_id: this.collectionId,
|
||||
collection_name: this.collection?.name,
|
||||
logged_in: !!this.authState,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<sl-icon
|
||||
@ -188,6 +197,13 @@ export class ShareCollection extends BtrixElement {
|
||||
href=${`/api/public/orgs/${this.slug}/collections/${this.collectionId}/download`}
|
||||
download
|
||||
?disabled=${!this.collection?.totalSize}
|
||||
@click=${() => {
|
||||
track(AnalyticsTrackEvent.DownloadPublicCollection, {
|
||||
org_slug: this.slug,
|
||||
collection_id: this.collectionId,
|
||||
collection_name: this.collection?.name,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<sl-icon name="cloud-download" slot="prefix"></sl-icon>
|
||||
${msg("Download Collection")}
|
||||
|
@ -24,6 +24,7 @@ import "./styles.css";
|
||||
|
||||
import { OrgTab, RouteNamespace, ROUTES } from "./routes";
|
||||
import type { UserInfo, UserOrg } from "./types/user";
|
||||
import { pageView, type AnalyticsTrackProps } from "./utils/analytics";
|
||||
import APIRouter, { type ViewState } from "./utils/APIRouter";
|
||||
import AuthService, {
|
||||
type AuthEventDetail,
|
||||
@ -158,6 +159,10 @@ export class App extends BtrixElement {
|
||||
);
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.trackPageView();
|
||||
}
|
||||
|
||||
willUpdate(changedProperties: Map<string, unknown>) {
|
||||
if (changedProperties.has("settings")) {
|
||||
AppStateService.updateSettings(this.settings || null);
|
||||
@ -296,6 +301,22 @@ export class App extends BtrixElement {
|
||||
} else {
|
||||
window.history.pushState(this.viewState, "", urlStr);
|
||||
}
|
||||
|
||||
this.trackPageView();
|
||||
}
|
||||
|
||||
trackPageView() {
|
||||
const { slug, collectionId } = this.viewState.params;
|
||||
const pageViewProps: AnalyticsTrackProps = {
|
||||
org_slug: slug || null,
|
||||
logged_in: !!this.authState,
|
||||
};
|
||||
|
||||
if (collectionId) {
|
||||
pageViewProps.collection_id = collectionId;
|
||||
}
|
||||
|
||||
pageView(pageViewProps);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
9
frontend/src/trackEvents.ts
Normal file
9
frontend/src/trackEvents.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* All available analytics tracking events
|
||||
*/
|
||||
|
||||
export enum AnalyticsTrackEvent {
|
||||
PageView = "pageview",
|
||||
CopyShareCollectionLink = "Copy share collection link",
|
||||
DownloadPublicCollection = "Download public collection",
|
||||
}
|
40
frontend/src/utils/analytics.ts
Normal file
40
frontend/src/utils/analytics.ts
Normal file
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Custom tracking for analytics.
|
||||
*
|
||||
* Any third-party analytics script will need to have been made
|
||||
* available through the `extra.js` injected by the server.
|
||||
*/
|
||||
|
||||
import { AnalyticsTrackEvent } from "../trackEvents";
|
||||
|
||||
export type AnalyticsTrackProps = {
|
||||
org_slug: string | null;
|
||||
collection_id?: string | null;
|
||||
collection_name?: string | null;
|
||||
logged_in?: boolean;
|
||||
};
|
||||
|
||||
export function track(
|
||||
event: `${AnalyticsTrackEvent}`,
|
||||
props?: AnalyticsTrackProps,
|
||||
) {
|
||||
// ANALYTICS_NAMESPACE is specified with webpack `DefinePlugin`
|
||||
const analytics = window.process.env.ANALYTICS_NAMESPACE
|
||||
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(window as any)[window.process.env.ANALYTICS_NAMESPACE]
|
||||
: null;
|
||||
|
||||
if (!analytics) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
analytics(event, { props });
|
||||
} catch (err) {
|
||||
console.debug(err);
|
||||
}
|
||||
}
|
||||
|
||||
export function pageView(props?: AnalyticsTrackProps) {
|
||||
track(AnalyticsTrackEvent.PageView, props);
|
||||
}
|
@ -9,6 +9,8 @@ import { playwrightLauncher } from "@web/test-runner-playwright";
|
||||
import glob from "glob";
|
||||
import { typescriptPaths as typescriptPathsPlugin } from "rollup-plugin-typescript-paths";
|
||||
|
||||
import defineConfig from "./config/define.js";
|
||||
|
||||
const commonjs = fromRollup(commonjsPlugin);
|
||||
const typescriptPaths = fromRollup(typescriptPathsPlugin);
|
||||
|
||||
@ -55,6 +57,7 @@ export default {
|
||||
ts: true,
|
||||
tsconfig: fileURLToPath(new URL("./tsconfig.json", import.meta.url)),
|
||||
target: "esnext",
|
||||
define: defineConfig,
|
||||
}),
|
||||
commonjs({
|
||||
include: [
|
||||
|
@ -11,6 +11,7 @@ const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
|
||||
const webpack = require("webpack");
|
||||
|
||||
const defineConfig = require("./config/define.js");
|
||||
// @ts-ignore
|
||||
const packageJSON = require("./package.json");
|
||||
|
||||
@ -24,11 +25,6 @@ require("dotenv").config({
|
||||
path: dotEnvPath,
|
||||
});
|
||||
|
||||
const WEBSOCKET_HOST =
|
||||
isDevServer && process.env.API_BASE_URL
|
||||
? new URL(process.env.API_BASE_URL).host
|
||||
: process.env.WEBSOCKET_HOST || "";
|
||||
|
||||
const DOCS_URL = process.env.DOCS_URL
|
||||
? new URL(process.env.DOCS_URL)
|
||||
: isDevServer
|
||||
@ -164,9 +160,7 @@ const main = {
|
||||
),
|
||||
}),
|
||||
|
||||
new webpack.DefinePlugin({
|
||||
"window.process.env.WEBSOCKET_HOST": JSON.stringify(WEBSOCKET_HOST),
|
||||
}),
|
||||
new webpack.DefinePlugin(defineConfig),
|
||||
|
||||
new webpack.optimize.LimitChunkCountPlugin({
|
||||
maxChunks: 12,
|
||||
|
@ -76,10 +76,10 @@ module.exports = [
|
||||
res.status(404).send(`{"error": "placeholder_for_replay"}`);
|
||||
});
|
||||
|
||||
// serve empty analytics script
|
||||
// Serve analytics script, which is set in prod as an env variable by the Helm chart
|
||||
server.app?.get("/extra.js", (req, res) => {
|
||||
res.set("Content-Type", "application/javascript");
|
||||
res.status(200).send("");
|
||||
res.status(200).send(process.env.INJECT_EXTRA || "");
|
||||
});
|
||||
|
||||
return middlewares;
|
||||
|
Loading…
Reference in New Issue
Block a user