Matomo integration (#7009)
This commit is contained in:
@@ -2,13 +2,14 @@
|
||||
/* global ga */
|
||||
import escape from "lodash/escape";
|
||||
import * as React from "react";
|
||||
import { IntegrationService } from "@shared/types";
|
||||
import { IntegrationService, PublicEnv } from "@shared/types";
|
||||
import env from "~/env";
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
// TODO: Refactor this component to allow injection from plugins
|
||||
const Analytics: React.FC = ({ children }: Props) => {
|
||||
// Google Analytics 3
|
||||
React.useEffect(() => {
|
||||
@@ -43,12 +44,16 @@ const Analytics: React.FC = ({ children }: Props) => {
|
||||
React.useEffect(() => {
|
||||
const measurementIds = [];
|
||||
|
||||
if (env.analytics.service === IntegrationService.GoogleAnalytics) {
|
||||
measurementIds.push(escape(env.analytics.settings?.measurementId));
|
||||
}
|
||||
if (env.GOOGLE_ANALYTICS_ID?.startsWith("G-")) {
|
||||
measurementIds.push(env.GOOGLE_ANALYTICS_ID);
|
||||
}
|
||||
|
||||
(env.analytics as PublicEnv["analytics"]).forEach((integration) => {
|
||||
if (integration.service === IntegrationService.GoogleAnalytics) {
|
||||
measurementIds.push(escape(integration.settings?.measurementId));
|
||||
}
|
||||
});
|
||||
|
||||
if (measurementIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -75,6 +80,32 @@ const Analytics: React.FC = ({ children }: Props) => {
|
||||
document.getElementsByTagName("head")[0]?.appendChild(script);
|
||||
}, []);
|
||||
|
||||
// Matomo
|
||||
React.useEffect(() => {
|
||||
(env.analytics as PublicEnv["analytics"]).forEach((integration) => {
|
||||
if (integration.service !== IntegrationService.Matomo) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-expect-error - Matomo global variable
|
||||
const _paq = (window._paq = window._paq || []);
|
||||
_paq.push(["trackPageView"]);
|
||||
_paq.push(["enableLinkTracking"]);
|
||||
(function () {
|
||||
const u = integration.settings?.instanceUrl;
|
||||
_paq.push(["setTrackerUrl", u + "matomo.php"]);
|
||||
_paq.push(["setSiteId", integration.settings?.measurementId]);
|
||||
const d = document,
|
||||
g = d.createElement("script"),
|
||||
s = d.getElementsByTagName("script")[0];
|
||||
g.type = "text/javascript";
|
||||
g.async = true;
|
||||
g.src = u + "matomo.js";
|
||||
s.parentNode?.insertBefore(g, s);
|
||||
})();
|
||||
});
|
||||
}, []);
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import sortBy from "lodash/sortBy";
|
||||
import {
|
||||
EmailIcon,
|
||||
ProfileIcon,
|
||||
@@ -18,7 +19,6 @@ import {
|
||||
import React, { ComponentProps } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { integrationSettingsPath } from "@shared/utils/routeHelpers";
|
||||
import GoogleIcon from "~/components/Icons/GoogleIcon";
|
||||
import ZapierIcon from "~/components/Icons/ZapierIcon";
|
||||
import PluginLoader from "~/utils/PluginLoader";
|
||||
import isCloudHosted from "~/utils/isCloudHosted";
|
||||
@@ -32,7 +32,6 @@ const ApiKeys = lazy(() => import("~/scenes/Settings/ApiKeys"));
|
||||
const Details = lazy(() => import("~/scenes/Settings/Details"));
|
||||
const Export = lazy(() => import("~/scenes/Settings/Export"));
|
||||
const Features = lazy(() => import("~/scenes/Settings/Features"));
|
||||
const GoogleAnalytics = lazy(() => import("~/scenes/Settings/GoogleAnalytics"));
|
||||
const Groups = lazy(() => import("~/scenes/Settings/Groups"));
|
||||
const Import = lazy(() => import("~/scenes/Settings/Import"));
|
||||
const Members = lazy(() => import("~/scenes/Settings/Members"));
|
||||
@@ -177,14 +176,6 @@ const useSettingsConfig = () => {
|
||||
group: t("Integrations"),
|
||||
icon: BuildingBlocksIcon,
|
||||
},
|
||||
{
|
||||
name: t("Google Analytics"),
|
||||
path: integrationSettingsPath("google-analytics"),
|
||||
component: GoogleAnalytics,
|
||||
enabled: can.update,
|
||||
group: t("Integrations"),
|
||||
icon: GoogleIcon,
|
||||
},
|
||||
{
|
||||
name: "Zapier",
|
||||
path: integrationSettingsPath("zapier"),
|
||||
@@ -196,30 +187,37 @@ const useSettingsConfig = () => {
|
||||
];
|
||||
|
||||
// Plugins
|
||||
Object.values(PluginLoader.plugins).map((plugin) => {
|
||||
const hasSettings = !!plugin.settings;
|
||||
const enabledInDeployment =
|
||||
!plugin.config?.deployments ||
|
||||
plugin.config.deployments.length === 0 ||
|
||||
(plugin.config.deployments.includes("cloud") && isCloudHosted) ||
|
||||
(plugin.config.deployments.includes("enterprise") && !isCloudHosted);
|
||||
const insertIndex = items.findIndex((i) => i.group === t("Integrations"));
|
||||
items.splice(
|
||||
insertIndex,
|
||||
0,
|
||||
...(sortBy(
|
||||
Object.values(PluginLoader.plugins),
|
||||
(plugin) => plugin.config?.priority ?? 0
|
||||
).map((plugin) => {
|
||||
const hasSettings = !!plugin.settings;
|
||||
const enabledInDeployment =
|
||||
!plugin.config?.deployments ||
|
||||
plugin.config.deployments.length === 0 ||
|
||||
(plugin.config.deployments.includes("community") && !isCloudHosted) ||
|
||||
(plugin.config.deployments.includes("cloud") && isCloudHosted) ||
|
||||
(plugin.config.deployments.includes("enterprise") && !isCloudHosted);
|
||||
|
||||
const item = {
|
||||
name: t(plugin.config.name),
|
||||
path: integrationSettingsPath(plugin.id),
|
||||
// TODO: Remove hardcoding of plugin id here
|
||||
group: plugin.id === "collections" ? t("Workspace") : t("Integrations"),
|
||||
component: plugin.settings,
|
||||
enabled:
|
||||
enabledInDeployment &&
|
||||
hasSettings &&
|
||||
(plugin.config.roles?.includes(user.role) || can.update),
|
||||
icon: plugin.icon,
|
||||
} as ConfigItem;
|
||||
|
||||
const insertIndex = items.findIndex((i) => i.group === t("Integrations"));
|
||||
items.splice(insertIndex, 0, item);
|
||||
});
|
||||
return {
|
||||
name: t(plugin.config.name),
|
||||
path: integrationSettingsPath(plugin.id),
|
||||
// TODO: Remove hardcoding of plugin id here
|
||||
group:
|
||||
plugin.id === "collections" ? t("Workspace") : t("Integrations"),
|
||||
component: plugin.settings,
|
||||
enabled:
|
||||
enabledInDeployment &&
|
||||
hasSettings &&
|
||||
(plugin.config.roles?.includes(user.role) || can.update),
|
||||
icon: plugin.icon,
|
||||
};
|
||||
}) as ConfigItem[])
|
||||
);
|
||||
|
||||
return items;
|
||||
}, [t, can.createApiKey, can.update, can.createImport, can.createExport]);
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
import find from "lodash/find";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import { IntegrationType, IntegrationService } from "@shared/types";
|
||||
import Integration from "~/models/Integration";
|
||||
import Button from "~/components/Button";
|
||||
import Heading from "~/components/Heading";
|
||||
import GoogleIcon from "~/components/Icons/GoogleIcon";
|
||||
import Input from "~/components/Input";
|
||||
import Scene from "~/components/Scene";
|
||||
import Text from "~/components/Text";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import SettingRow from "./components/SettingRow";
|
||||
|
||||
type FormData = {
|
||||
measurementId: string;
|
||||
};
|
||||
|
||||
function GoogleAnalytics() {
|
||||
const { integrations } = useStores();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const integration = find(integrations.orderedData, {
|
||||
type: IntegrationType.Analytics,
|
||||
service: IntegrationService.GoogleAnalytics,
|
||||
}) as Integration<IntegrationType.Analytics> | undefined;
|
||||
|
||||
const {
|
||||
register,
|
||||
reset,
|
||||
handleSubmit: formHandleSubmit,
|
||||
formState,
|
||||
} = useForm<FormData>({
|
||||
mode: "all",
|
||||
defaultValues: {
|
||||
measurementId: integration?.settings.measurementId,
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
void integrations.fetchPage({
|
||||
type: IntegrationType.Analytics,
|
||||
});
|
||||
}, [integrations]);
|
||||
|
||||
React.useEffect(() => {
|
||||
reset({ measurementId: integration?.settings.measurementId });
|
||||
}, [integration, reset]);
|
||||
|
||||
const handleSubmit = React.useCallback(
|
||||
async (data: FormData) => {
|
||||
try {
|
||||
if (data.measurementId) {
|
||||
await integrations.save({
|
||||
id: integration?.id,
|
||||
type: IntegrationType.Analytics,
|
||||
service: IntegrationService.GoogleAnalytics,
|
||||
settings: {
|
||||
measurementId: data.measurementId,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await integration?.delete();
|
||||
}
|
||||
|
||||
toast.success(t("Settings saved"));
|
||||
} catch (err) {
|
||||
toast.error(err.message);
|
||||
}
|
||||
},
|
||||
[integrations, integration, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<Scene title={t("Google Analytics")} icon={<GoogleIcon />}>
|
||||
<Heading>{t("Google Analytics")}</Heading>
|
||||
|
||||
<Text as="p" type="secondary">
|
||||
<Trans>
|
||||
Add a Google Analytics 4 measurement ID to send document views and
|
||||
analytics from the workspace to your own Google Analytics account.
|
||||
</Trans>
|
||||
</Text>
|
||||
<form onSubmit={formHandleSubmit(handleSubmit)}>
|
||||
<SettingRow
|
||||
label={t("Measurement ID")}
|
||||
name="measurementId"
|
||||
description={t(
|
||||
'Create a "Web" stream in your Google Analytics admin dashboard and copy the measurement ID from the generated code snippet to install.'
|
||||
)}
|
||||
border={false}
|
||||
>
|
||||
<Input placeholder="G-XXXXXXXXX1" {...register("measurementId")} />
|
||||
</SettingRow>
|
||||
|
||||
<Button type="submit" disabled={formState.isSubmitting}>
|
||||
{formState.isSubmitting ? `${t("Saving")}…` : t("Save")}
|
||||
</Button>
|
||||
</form>
|
||||
</Scene>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(GoogleAnalytics);
|
||||
@@ -8,6 +8,7 @@ interface Plugin {
|
||||
description: string;
|
||||
roles?: UserRole[];
|
||||
deployments?: string[];
|
||||
priority?: number;
|
||||
};
|
||||
settings: React.FC;
|
||||
icon: React.FC<{ size?: number; fill?: string }>;
|
||||
|
||||
Reference in New Issue
Block a user