Skip to content

Commit

Permalink
feat: add umami
Browse files Browse the repository at this point in the history
  • Loading branch information
MiniDigger committed Dec 23, 2024
1 parent a5a6827 commit 0c12866
Show file tree
Hide file tree
Showing 14 changed files with 116 additions and 14 deletions.
1 change: 1 addition & 0 deletions chart/templates/secret-hangar-frontend.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ stringData:
#NITRO_CLUSTER_WORKERS: "4"
SENTRY_ENV: "{{ .Values.backend.config.sentry.environment }}"
NUXT_PUBLIC_TURNSTILE_SITE_KEY: "{{ .Values.frontend.config.turnstileSiteKey }}"
UMAMI_WEBSITE_ID: "{{ .Values.frontend.config.umamiWebsiteId }}"
1 change: 1 addition & 0 deletions chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ frontend:
configEnv: "hangar.test"
backendHost: "http://hangar-backend:8080"
turnstileSiteKey: "todo"
umamiWebsiteId: "todo"

backend:
replicaCount: 1
Expand Down
2 changes: 2 additions & 0 deletions frontend/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const backendDataHost = process.env.BACKEND_DATA_HOST || (local ? "http://localh
const allowIndexing = process.env.HANGAR_ALLOW_INDEXING || "true";
const sentryDSN = process.env.SENTRY_DSN || "https://801c6e3ec217457e94b8d360e861242d@o4504989579804672.ingest.sentry.io/4504989584850944";
const sentryEnvironment = process.env.SENTRY_ENV || "local";
const umamiWebsiteId = process.env.UMAMI_WEBSIDE_ID || "8530c6aa-8fb3-4421-8503-4e8de6bc19ef"; // hangar local;

// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
Expand Down Expand Up @@ -43,6 +44,7 @@ export default defineNuxtConfig({
backendHost,
public: {
allowIndexing,
umamiWebsiteId,
sentry: {
dsn: sentryDSN,
environment: sentryEnvironment,
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import "./assets/css/main.css";
// popper needs this?
import "regenerator-runtime/runtime";
import type { HangarNuxtError } from "~/types/components/error";
import { identify } from "~/composables/useTracking";
// keep in sync with error.vue, cause reasons
const runtimeConfig = useRuntimeConfig();
Expand All @@ -11,6 +12,7 @@ const settingsStore = useSettingsStore();
await settingsStore.loadSettingsClient();
useAccentColor();
settingsLog("render for user", authStore.user?.name, "with darkmode", settingsStore.darkMode);
identify();
// for some dum reason useHead will not always replace the "light" from server side with "dark" from client side so we just force it.
if (import.meta.client) {
Expand All @@ -31,6 +33,13 @@ useHead({
class: "background-body text-[#262626] dark:text-[#E0E6f0]",
},
meta: [{ name: "robots", content: runtimeConfig.public.allowIndexing === "false" ? "noindex,nofollow" : "index,follow" }],
script: [
{
src: "https://trk.papermc.io/api/init",
defer: true,
"data-website-id": useRuntimeConfig().public.umamiWebsiteId,
},
],
});
onErrorCaptured((err) => {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/Homepage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ useSeo(
class="rounded-l-md md:rounded-md p-4 basis-full min-w-0 dark:bg-gray-700"
type="text"
:placeholder="i18n.t('hangar.projectSearch.query', [projects?.pagination.count])"
v-on="useTracking('homepage-search', { platformName })"
/>
<div class="md:hidden flex">
<Menu>
Expand Down
40 changes: 37 additions & 3 deletions frontend/src/components/design/Steps.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts" setup>
import type { Step } from "~/types/components/design/Steps";
import { track } from "~/composables/useTracking";
const router = useRouter();
const i18n = useI18n();
Expand All @@ -16,13 +17,24 @@ const props = defineProps<{
modelValue: string;
steps: Step[];
buttonLangKey: string;
trackingName: string;
}>();
const v = useVuelidate();
const activeStep = computed(() => props.steps.find((s) => s.value === internalValue.value));
const activeStepIndex = computed(() => props.steps.indexOf(activeStep.value as Step) + 1);
watch(
() => activeStep.value,
() => {
track("funnel", props.trackingName + "-step-" + activeStepIndex.value + "-" + activeStep.value?.value);
},
{
immediate: true,
}
);
const loading = ref(false);
const disableBack = computed(() => loading.value || (activeStep.value?.disableBack === undefined ? false : unref(activeStep.value?.disableBack)));
const disableNext = computed(() => loading.value || (activeStep.value?.disableNext === undefined ? v.value.$invalid : unref(activeStep.value?.disableNext)));
Expand Down Expand Up @@ -105,13 +117,35 @@ async function goto(step: Step) {
<div v-for="step in steps" :key="step.value">
<slot v-if="internalValue === step.value" :name="step.value" />
</div>
<Button v-if="showBack && activeStepIndex === 1" button-type="red" :disabled="disableBack" size="medium" class="mt-3 mr-2" @click="back">
<Button
v-if="showBack && activeStepIndex === 1"
button-type="red"
:disabled="disableBack"
size="medium"
class="mt-3 mr-2"
@click="back"
v-on="useTracking(trackingName + '-back', { step: internalValue })"
>
{{ i18n.t(buttonLangKey + activeStepIndex + ".back") }}
</Button>
<Button v-else-if="showBack" :disabled="disableBack" size="medium" class="mt-3 mr-2" @click="back">
<Button
v-else-if="showBack"
:disabled="disableBack"
size="medium"
class="mt-3 mr-2"
@click="back"
v-on="useTracking(trackingName + '-back', { step: internalValue })"
>
{{ i18n.t(buttonLangKey + activeStepIndex + ".back") }}
</Button>
<Button v-if="showNext" :disabled="disableNext" size="medium" class="mt-3" @click="next">
<Button
v-if="showNext"
:disabled="disableNext"
size="medium"
class="mt-3"
@click="next"
v-on="useTracking(trackingName + '-next', { step: internalValue })"
>
{{ i18n.t(buttonLangKey + activeStepIndex + ".continue") }}
</Button>
</Card>
Expand Down
24 changes: 17 additions & 7 deletions frontend/src/components/layout/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ function isRecent(date: string): boolean {
<nav class="max-w-screen-xl mx-auto flex flex-wrap justify-end px-4 py-2 gap-3">
<!-- Left side items -->
<div class="flex items-center gap-4">
<Popover v-slot="{ close }" class="relative">
<PopoverButton id="menu-button" v-slot="{ open }" class="flex" aria-label="Menu">
<Popover v-slot="{ close, open }" class="relative">
<PopoverButton id="menu-button" aria-label="Menu" v-on="useTracking('nav-burger-button', { open })" class="flex">
<icon-mdi-menu class="transition-transform text-[1.2em]" :class="open ? 'transform rotate-90' : ''" />
</PopoverButton>

Expand All @@ -155,6 +155,7 @@ function isRecent(date: string): boolean {
:to="{ name: link.link } as RouteLocationRaw"
class="flex items-center rounded-md px-6 py-2"
hover="text-primary-500 bg-primary-0"
v-on="useTracking('nav-burger-link', { link: link.link })"
@click="close()"
>
<component :is="link.icon" class="mr-3 text-[1.2em]" />
Expand All @@ -170,6 +171,7 @@ function isRecent(date: string): boolean {
:to="{ name: link.link } as RouteLocationRaw"
class="flex items-center rounded-md px-6 py-2"
hover="text-primary-500 bg-primary-0"
v-on="useTracking('nav-burger-link', { link: link.link })"
@click="close()"
>
<component :is="link.icon" class="mr-3 text-[1.2em]" />
Expand All @@ -184,6 +186,7 @@ function isRecent(date: string): boolean {
:key="link.label"
class="flex items-center rounded-md px-6 py-2 hover:(text-primary-500 bg-primary-0)"
:href="link.link"
v-on="useTracking('nav-burger-link', { link: link.link })"
>
<component :is="link.icon" class="mr-3 text-[1.2em]" />
{{ link.label }}
Expand All @@ -193,7 +196,7 @@ function isRecent(date: string): boolean {
</Popover>

<!-- Site logo -->
<NuxtLink to="/" class="flex-shrink-0">
<NuxtLink to="/" class="flex-shrink-0" v-on="useTracking('nav-logo')">
<img alt="Hangar Logo" :src="hangarLogo" height="34" width="32" />
</NuxtLink>

Expand All @@ -205,6 +208,7 @@ function isRecent(date: string): boolean {
:to="{ name: navBarLink.link } as RouteLocationRaw"
class="header-link relative"
after="absolute content-empty block w-0 top-30px left-1/10 h-4px rounded-8px"
v-on="useTracking('nav-desktop-link', { link: navBarLink.link })"
>
{{ navBarLink.label }}
</NuxtLink>
Expand All @@ -217,10 +221,10 @@ function isRecent(date: string): boolean {
<!-- Right side items -->
<div class="flex items-center gap-2">
<div v-if="authStore.user" class="flex items-center lt-sm:hidden">
<DropdownButton :name="t('nav.new.title')">
<DropdownButton :name="t('nav.new.title')" v-on="useTracking('nav-create-dropdwon')">
<template #default="{ close }">
<DropdownItem to="/new" @click="close()">{{ t("nav.new.project") }}</DropdownItem>
<DropdownItem to="/neworganization" @click="close()">{{ t("nav.new.organization") }}</DropdownItem>
<DropdownItem to="/new" @click="close()" v-on="useTracking('nav-new')">{{ t("nav.new.project") }}</DropdownItem>
<DropdownItem to="/neworganization" @click="close()" v-on="useTracking('nav-new-org')">{{ t("nav.new.organization") }}</DropdownItem>
</template>
</DropdownButton>
</div>
Expand All @@ -229,6 +233,7 @@ function isRecent(date: string): boolean {
hover="text-primary-500 bg-primary-0 dark:(text-white bg-zinc-700)"
aria-label="Toogle dark mode"
@click="settings.toggleDarkMode()"
v-on="useTracking('nav-theme', { darkMode: settings.darkMode })"
>
<icon-mdi-weather-night v-if="settings.darkMode" class="text-[1.2em]" />
<icon-mdi-white-balance-sunny v-else class="text-[1.2em]" />
Expand All @@ -239,6 +244,7 @@ function isRecent(date: string): boolean {
class="flex items-center gap-2 rounded-md p-2 hover:(text-primary-500 bg-primary-0 dark:(text-white bg-zinc-700))"
aria-label="Notifications"
@click="updateNotifications"
v-on="useTracking('nav-notifications', { unread: unreadNotifications })"
>
<IconMdiBellOutline v-show="unreadNotifications === 0" class="text-[1.2em]" />
<div v-show="unreadNotifications !== 0" class="relative">
Expand Down Expand Up @@ -308,7 +314,11 @@ function isRecent(date: string): boolean {
<!-- Profile dropdown -->
<div v-if="authStore.user">
<Popper placement="bottom-end">
<button class="flex items-center gap-2 rounded-md p-2 hover:(text-primary-500 bg-primary-0 dark:(text-white bg-zinc-700))" @click="updateNavData">
<button
class="flex items-center gap-2 rounded-md p-2 hover:(text-primary-500 bg-primary-0 dark:(text-white bg-zinc-700))"
@click="updateNavData"
v-on="useTracking('nav-profile-dropdown')"
>
<UserAvatar :username="authStore.user.name" :avatar-url="authStore.user.avatarUrl" size="xs" :disable-link="true" />
{{ authStore.user.name }}
</button>
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/components/projects/DownloadButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ function formatVersionRange(versions: string[]): string {
rel="noopener noreferrer"
@click="trackDownload(p, pinnedVersion)"
@click.middle="trackDownload(p, pinnedVersion)"
v-on="useTracking('download-link', { pinned: true, dropdown: false, mainchannel: false, platform: p, project: project.name })"
>
<Button :size="small ? 'medium' : 'large'">
<IconMdiDownloadOutline />
Expand All @@ -109,6 +110,7 @@ function formatVersionRange(versions: string[]): string {
rel="noopener noreferrer"
@click="trackDownload(p, pinnedVersion)"
@click.middle="trackDownload(p, pinnedVersion)"
v-on="useTracking('download-link', { pinned: false, dropdown: true, mainchannel: false, platform: p, project: project.name })"
>
<PlatformLogo :platform="p as Platform" :size="24" class="mr-1 flex-shrink-0" />
{{ useBackendData.platforms.get(p as Platform)?.name }}
Expand All @@ -125,6 +127,7 @@ function formatVersionRange(versions: string[]): string {
rel="noopener noreferrer"
@click="trackDownload(singlePlatform, singleVersion)"
@click.middle="trackDownload(singlePlatform, singleVersion)"
v-on="useTracking('download-link', { pinned: false, dropdown: false, mainchannel: false, platform: singlePlatform, project: project.name })"
>
<Button :size="small ? 'medium' : 'large'">
<div class="flex flex-col" :class="{ '-mb-0.5': showSinglePlatform }">
Expand Down Expand Up @@ -160,6 +163,7 @@ function formatVersionRange(versions: string[]): string {
rel="noopener noreferrer"
@click="trackDownload(p, version)"
@click.middle="trackDownload(p, version)"
v-on="useTracking('download-link', { pinned: false, dropdown: true, mainchannel: false, platform: p, project: project.name })"
>
<PlatformLogo :platform="p as Platform" :size="24" class="mr-1 flex-shrink-0" />
{{ useBackendData.platforms.get(p as Platform)?.name }}
Expand All @@ -185,6 +189,7 @@ function formatVersionRange(versions: string[]): string {
rel="noopener noreferrer"
@click="trackDownload(p, v)"
@click.middle="trackDownload(p, v)"
v-on="useTracking('download-link', { pinned: false, dropdown: true, mainchannel: true, platform: p, project: project.name })"
>
<PlatformLogo :platform="p as Platform" :size="24" class="mr-1 flex-shrink-0" />
{{ useBackendData.platforms.get(p as Platform)?.name }}
Expand Down
30 changes: 30 additions & 0 deletions frontend/src/composables/useTracking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export default function useTracking(elementName: MaybeRefOrGetter<string>, additionalData: MaybeRefOrGetter<Record<string, MaybeRefOrGetter<unknown>>> = {}) {
return {
click: (e: MouseEvent) => track("click", elementName, additionalData),
change: (e: MouseEvent) => track("change", elementName, additionalData),
};
}

export function track(type: string, elementName: MaybeRefOrGetter<string>, additionalData: MaybeRefOrGetter<Record<string, MaybeRefOrGetter<unknown>>> = {}) {
if (import.meta.server) return;
const name = toValue(elementName);
const data: Record<string, unknown> = { type };
for (const [k, v] of Object.entries(toValue(additionalData))) {
data[k] = toValue(v);
}
data["type"] = type;
window.umami.track(name, data);
}

export function identify() {
if (import.meta.server) return;
const authStore = useAuthStore();
const settingsStore = useSettingsStore();
const i18n = useNuxtApp().$i18n;

const props: Record<string, unknown> = {};
props.user = authStore.user ? authStore.user.name : "<anonymous>";
props.theme = settingsStore.darkMode ? "dark" : "light";
props.language = i18n.locale.value;
window.umami.identify(props);
}
2 changes: 1 addition & 1 deletion frontend/src/pages/[user]/[project]/versions/new.vue
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ useSeo(

<template>
<div>
<Steps v-model="selectedStep" :steps="steps" button-lang-key="version.new.steps.">
<Steps v-model="selectedStep" :steps="steps" button-lang-key="version.new.steps." tracking-name="new-version">
<template #artifact>
<p class="mb-4">{{ t("version.new.form.artifactTitle") }}</p>
<div class="flex mb-8 items-center">
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/new.vue
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ function createProject() {
</script>

<template>
<Steps v-model="selectedStep" :steps="steps" button-lang-key="project.new.step">
<Steps v-model="selectedStep" :steps="steps" button-lang-key="project.new.step" tracking-name="new-project">
<template #tos>
<div class="flex-col flex inline-flex">
<Alert class="mb-4">
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/pages/privacy.vue
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ Cloudflare
This Website is protected by various Cloudflare technologies. You can view their Privacy Policy here: https://www.cloudflare.com/privacypolicy/
Sentry
----------
This Website uses Sentry for error tracking. You can view their Privacy Policy here: https://sentry.io/privacy/
Changes to this Privacy Policy
------------------------------
Expand All @@ -120,7 +125,7 @@ Contact Us
If you have any questions about this Privacy Policy, You can contact Us via email at [email protected].
Last updated: December 25, 2022
Last updated: December 23, 2024
`;
useSeo(computed(() => ({ title: i18n.t("pages.privacypolicyTitle"), route })));
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/tools/importer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ useSeo(computed(() => ({ title: t("importer.title"), route })));
<PageTitle>{{ t("importer.title") }}</PageTitle>
<p v-if="!loggedIn">{{ t("importer.notLoggedIn") }}</p>

<Steps v-else v-model="selectedStep" :steps="steps" button-lang-key="importer.step">
<Steps v-else v-model="selectedStep" :steps="steps" button-lang-key="importer.step" tracking-name="importer">
<template #intro>
<p>{{ t("importer.step1.text1") }}</p>
<p class="inline-flex items-center space-x-2">
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/types/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ declare global {
interface Window {
hangarLoaded?: boolean;
hangarDebug?: Record<string, () => any>;
umami: {
track: (eventName: string, eventData: Record<string, unknown>) => void;
identify: (sessionData: Record<string, unknown>) => void;
};
}
}

Expand Down

0 comments on commit 0c12866

Please sign in to comment.