Skip to content

Commit

Permalink
Improve Plausible (use sendBeacon, more events)
Browse files Browse the repository at this point in the history
  • Loading branch information
fyrk committed Oct 27, 2023
1 parent 9dadc21 commit d088804
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 28 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "src/helpers/plausible-tracker-sendbeacon"]
path = src/helpers/plausible-tracker-sendbeacon
url = https://github.com/orgrosua/plausible-tracker.git
12 changes: 6 additions & 6 deletions src/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Home from "./Home"
import { createSpotifyApi } from "./api"
import App from "./app/App"
import { Fallback } from "./components"
import { trackPlausibleEvent } from "./helpers/plausible"
import { PlausibleEvent, trackPlausibleEvent } from "./helpers/plausible"

export default function Main() {
const [spotify, setSpotify] = useState<SpotifyApi>(null)
Expand All @@ -26,7 +26,7 @@ export default function Main() {
const { authenticated } = await sdk.authenticate()
if (authenticated && !spotify) {
setSpotify(sdk)
trackPlausibleEvent("Authenticated")
trackPlausibleEvent(PlausibleEvent.Authenticated)
}
} catch (e) {
console.info("User is not authenticated", e)
Expand All @@ -53,7 +53,7 @@ export default function Main() {
Sentry.captureMessage("Authentication error", { extra: { error } })
}

trackPlausibleEvent("Authentication error", { props: { error } })
trackPlausibleEvent(PlausibleEvent.AuthError, { props: { error } })
}
}, [])

Expand All @@ -65,7 +65,7 @@ export default function Main() {
spotify.logOut()
setSpotify(null)
if (state !== "finished") {
trackPlausibleEvent("Logout", { props: { state } })
trackPlausibleEvent(PlausibleEvent.Logout, { props: { state } })
}
}}
spotify={spotify}
Expand All @@ -79,7 +79,7 @@ export default function Main() {
<Home
authError={authError}
onLogin={async () => {
trackPlausibleEvent("Login click")
trackPlausibleEvent(PlausibleEvent.LoginClick)
try {
setAuthError(null)
const sdk = createSpotifyApi()
Expand All @@ -93,7 +93,7 @@ export default function Main() {
// @ts-ignore
const error = new Error("Authenticating failed", { cause: e })
Sentry.captureException(error)
trackPlausibleEvent("Authentication failed")
trackPlausibleEvent(PlausibleEvent.AuthFailed)
}
}}
/>
Expand Down
15 changes: 13 additions & 2 deletions src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as Sentry from "@sentry/react"
import { SpotifyApi, User } from "@spotify/web-api-ts-sdk"
import { useEffect, useState } from "preact/hooks"
import { BaseButton, Button, Fallback, Scaffold } from "../components"
import { trackPlausibleEventPlaylistsUpdated } from "../helpers/plausible"
import {
NO_PROGRESS,
PlaylistSelection as PlaylistWithSelection,
Expand Down Expand Up @@ -76,7 +77,7 @@ export default function App({
}

/**
* Move app's content here so error fallback still shows header and logout button.
* Move app's content here so error fallback still shows header and logout button
*/
const AppContent = ({
state,
Expand Down Expand Up @@ -120,7 +121,7 @@ const AppContent = ({
return (
<PlaylistEditor
scanResult={scanResult}
onDoReplace={async (selectedTracks, selectedVariants) => {
onDoReplace={async (selectedTracks, selectedVariants, metrics) => {
try {
setProgress(NO_PROGRESS)
setState("replacing")
Expand Down Expand Up @@ -149,6 +150,16 @@ const AppContent = ({
await replaceTracks(spotify, selectedPlaylists, setProgress),
)
setState("finished")

trackPlausibleEventPlaylistsUpdated(
selectedPlaylists.length,
selectedPlaylists.reduce(
(p, c) => p + c.stolenIdsToRemove.length,
0,
),
metrics.totalFoundTracks,
metrics.selectionCategories,
)
} catch (e) {
setAsyncError(e)
}
Expand Down
35 changes: 32 additions & 3 deletions src/app/playlisteditor/PlaylistEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,17 @@ export interface ReplaceViewData {

const SELECTION_CATEGORIES = [
{
name: "replace-og-live",
label: "Replace original live releases",
predicate: (s: StolenVariants) => s.isLive,
},
{
name: "replace-og-remixes",
label: "Replace original remixes",
predicate: (s: StolenVariants) => s.isRemix,
},
{
name: "replace-og-special",
label: (
<>
Replace original special releases that don’t have a Taylor’s Version
Expand All @@ -46,6 +49,12 @@ export default function PlaylistEditor({
onDoReplace: (
selectedTracks: Set<string>[],
selectedVariants: string[][],
metrics: {
selectionCategories: {
[name: string]: "yes" | "no" | "some" | "nonexistent"
}
totalFoundTracks: number
},
) => void
spotify: SpotifyApi
}) {
Expand Down Expand Up @@ -134,10 +143,11 @@ export default function PlaylistEditor({
return { exists, isAllSelected, isIndeterminate, toggle }
}

const selectionCategories = SELECTION_CATEGORIES.map(c => ({
const _selectionCategories = SELECTION_CATEGORIES.map(c => ({
...c,
..._createCategory(c.predicate),
})).filter(c => c.exists)
}))
const selectionCategories = _selectionCategories.filter(c => c.exists)

// ========================
// VARIANT EDITING
Expand Down Expand Up @@ -202,7 +212,26 @@ export default function PlaylistEditor({
<div class="mb-12 text-center">
<Button
class="bg-accent disabled:bg-neutral-600"
onClick={() => onDoReplace(selectedTracks, selectedVariants)}
onClick={() =>
onDoReplace(selectedTracks, selectedVariants, {
selectionCategories: Object.fromEntries(
_selectionCategories.map(c => [
c.name,
c.exists
? c.isAllSelected
? "yes"
: c.isIndeterminate
? "some"
: "no"
: "nonexistent",
]),
),
totalFoundTracks: playlists.reduce(
(p, c) => p + c.tracks.length,
0,
),
})
}
disabled={songsToReplaceCount === 0}
>
{songsToReplaceCount === 0 ? (
Expand Down
81 changes: 64 additions & 17 deletions src/helpers/plausible.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,86 @@
import * as Sentry from "@sentry/react"
import Plausible, { EventOptions, PlausibleOptions } from "plausible-tracker"
// use client from PR [0]
// until plausible-tracker supports navigator.sendBeacon [1]
// and doesn't break target="_blank" [2]
// [0] https://github.com/plausible/plausible-tracker/pull/54
// [1] https://github.com/plausible/plausible-tracker/issues/12
// [2] https://github.com/plausible/plausible-tracker/issues/12
import Plausible, {
EventOptions,
PlausibleOptions,
} from "./plausible-tracker-sendbeacon/src"

export enum PlausibleEvent {
LoginClick = "Login click", // User clicked Login button
AuthFailed = "Authentication failed", // Authentication after clicking Login button failed
AuthError = "Authentication error", // Authentication error reported by Spotify after redirection
Authenticated = "Authenticated", // User entered page already authenticated (i.e. after redirect from Spotify or page reload)
Logout = "Logout", // User clicked Logout button
PlaylistsUpdated = "Playlists updated", // Playlists updated successfully
}

// not exported in plausible-tracker
type TrackEvent = (
eventName: string,
options?: EventOptions,
eventData?: PlausibleOptions,
) => void

let _trackPlausibleEvent: TrackEvent | undefined

export const trackPlausibleEvent: TrackEvent = (
eventName: string,
options?: EventOptions,
eventData?: PlausibleOptions,
) => {
try {
if (!_trackPlausibleEvent) setupPlausible()
_trackPlausibleEvent && _trackPlausibleEvent(eventName, options, eventData)
} catch (e) {
Sentry.captureException(e)
}
}

export default function setupPlausible() {
if (import.meta.env.VITE_PLAUSIBLE_DOMAIN) {
const plausible = Plausible({
domain: import.meta.env.VITE_PLAUSIBLE_DOMAIN,
apiHost:
import.meta.env.VITE_PLAUSIBLE_API_HOST || "https://plausible.io",
trackLocalhost: true,
useSendBeacon: true,
})
plausible.enableAutoPageviews()
// breaks target="_blank" https://github.com/plausible/plausible-tracker/issues/12
//plausible.enableAutoOutboundTracking()
plausible.enableAutoOutboundTracking()
_trackPlausibleEvent = plausible.trackEvent
}
}

export const trackPlausibleEvent = (
eventName: PlausibleEvent,
eventData?: EventOptions,
options?: PlausibleOptions,
) => {
try {
if (!_trackPlausibleEvent) setupPlausible()
_trackPlausibleEvent && _trackPlausibleEvent(eventName, eventData, options)
} catch (e) {
Sentry.captureException(e)
}
}

const floorToFirstDigit = (n: number) => {
if (n === 0) return 0
const x = 10 ** Math.floor(Math.log10(n))
return Math.floor(n / x) * x
}

const formatRoundPercent = (n: number) => {
if (n <= 0) return "0%"
if (n >= 1) return "100%"
return `<${Math.ceil(n / 0.1) * 10}%`
}

export const trackPlausibleEventPlaylistsUpdated = (
replacedPlaylistsCount: number,
replacedTrackCount: number,
totalTrackCount: number,
selectionCategories: { [name: string]: string },
) => {
trackPlausibleEvent(PlausibleEvent.PlaylistsUpdated, {
props: {
"count-playlists": floorToFirstDigit(replacedPlaylistsCount),
"count-tracks": floorToFirstDigit(replacedTrackCount),
"percent-selected": formatRoundPercent(
replacedTrackCount / totalTrackCount,
),
...selectionCategories,
},
})
}

0 comments on commit d088804

Please sign in to comment.