feat: add analytics (#528)

* feat: add analytics

* feat: send data to umami

* refactor: fix format

* fix: click behavior of switches

* refactor: format

* chore: rerun ci

---------

Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
This commit is contained in:
Manuel
2024-05-26 17:18:25 +02:00
committed by GitHub
parent d57b771a17
commit 7e64d39693
17 changed files with 558 additions and 179 deletions

View File

@@ -14,6 +14,7 @@
"with-env": "dotenv -e ../../.env --"
},
"dependencies": {
"@homarr/analytics": "workspace:^0.1.0",
"@homarr/api": "workspace:^0.1.0",
"@homarr/auth": "workspace:^0.1.0",
"@homarr/common": "workspace:^0.1.0",

View File

@@ -11,6 +11,7 @@ import { auth } from "@homarr/auth/next";
import { ModalProvider } from "@homarr/modals";
import { Notifications } from "@homarr/notifications";
import { Analytics } from "~/components/layout/analytics";
import { JotaiProvider } from "./_client-providers/jotai";
import { NextInternationalProvider } from "./_client-providers/next-international";
import { AuthProvider } from "./_client-providers/session";
@@ -76,6 +77,7 @@ export default function Layout(props: { children: React.ReactNode; params: { loc
<html lang="en" suppressHydrationWarning>
<head>
<ColorSchemeScript defaultColorScheme={colorScheme} />
<Analytics />
</head>
<body className={["font-sans", fontSans.variable].join(" ")}>
<StackedProvider>

View File

@@ -96,18 +96,23 @@ const SwitchSetting = ({
title: string;
text: ReactNode;
}) => {
const disabled = formKey !== "enableGeneral" && !form.values.enableGeneral;
const handleClick = React.useCallback(() => {
if (disabled) {
return;
}
form.setFieldValue(formKey, !form.values[formKey]);
}, [form, formKey]);
}, [form, formKey, disabled]);
return (
<UnstyledButton onClick={handleClick}>
<Group ms={ms} justify="space-between" gap="lg" align="center" wrap="nowrap">
<Group ms={ms} justify="space-between" gap="lg" align="center" wrap="nowrap">
<UnstyledButton style={{ flexGrow: 1 }} onClick={handleClick}>
<Stack gap={0}>
<Text fw="bold">{title}</Text>
<Text c="gray.5">{text}</Text>
</Stack>
<Switch {...form.getInputProps(formKey, { type: "checkbox" })} />
</Group>
</UnstyledButton>
</UnstyledButton>
<Switch disabled={disabled} onClick={handleClick} checked={form.values[formKey] && !disabled} />
</Group>
);
};

View File

@@ -0,0 +1,14 @@
import Script from "next/script";
import { UMAMI_WEBSITE_ID } from "@homarr/analytics";
import { api } from "@homarr/api/server";
export const Analytics = async () => {
const analytics = await api.serverSettings.getAnalytics();
if (analytics.enableGeneral) {
return <Script src="https://umami.homarr.dev/script.js" data-website-id={UMAMI_WEBSITE_ID} defer />;
}
return <></>;
};

View File

@@ -27,6 +27,7 @@
"@homarr/redis": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@homarr/analytics": "workspace:^0.1.0",
"dotenv": "^16.4.5",
"node-cron": "^3.0.3",
"superjson": "2.2.1"

View File

@@ -1,4 +1,5 @@
import { iconsUpdaterJob } from "~/jobs/icons-updater";
import { analyticsJob } from "./jobs/analytics";
import { queuesJob } from "./jobs/queue";
import { createJobGroup } from "./lib/cron-job/group";
@@ -8,4 +9,5 @@ export const jobs = createJobGroup({
// This job is used to process queues.
queues: queuesJob,
iconsUpdater: iconsUpdaterJob,
analytics: analyticsJob,
});

View File

@@ -0,0 +1,29 @@
import SuperJSON from "superjson";
import { sendServerAnalyticsAsync } from "@homarr/analytics";
import { db, eq } from "@homarr/db";
import { serverSettings } from "@homarr/db/schema/sqlite";
import { EVERY_WEEK } from "~/lib/cron-job/constants";
import { createCronJob } from "~/lib/cron-job/creator";
import type { defaultServerSettings } from "../../../../packages/server-settings";
export const analyticsJob = createCronJob(EVERY_WEEK, {
runOnStart: true,
}).withCallback(async () => {
const analyticSetting = await db.query.serverSettings.findFirst({
where: eq(serverSettings.settingKey, "analytics"),
});
if (!analyticSetting) {
return;
}
const value = SuperJSON.parse<(typeof defaultServerSettings)["analytics"]>(analyticSetting.value);
if (!value.enableGeneral) {
return;
}
await sendServerAnalyticsAsync();
});