added ga4 google analytics (#5126)

* added ga4 google analytics

* added ga4 script as TODO

* get analytics app working

* Remove stale comment

* Fix types

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
This commit is contained in:
Peer Richelsen 2022-10-22 10:39:36 +01:00 committed by GitHub
parent 79df03de49
commit 9f1341e94e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 233 additions and 19 deletions

View File

@ -1,35 +1,40 @@
import Script from "next/script";
import { getEventTypeAppData } from "@calcom/app-store/utils";
import { trackingApps } from "./eventTypeAnalytics";
// TODO: Maintain it from config.json maybe
const trackingApps: Record<string, Record<string, unknown>> = {
fathom: {
src: "https://cdn.usefathom.com/script.js",
"data-site": "{TRACKING_ID}",
},
};
export type AppScript = {attrs?: Record<string, string>} & ({src: undefined, content?: string} | {src?: string, content:undefined})
export default function BookingPageTagManager({
eventType,
}: {
eventType: Parameters<typeof getEventTypeAppData>[0];
}) {
return (
<>
{Object.entries(trackingApps).map(([appId, scriptConfig]) => {
const trackingId = getEventTypeAppData(eventType, "fathom")?.trackingId;
const trackingId = getEventTypeAppData(eventType, appId as keyof typeof trackingApps)?.trackingId;
if (!trackingId) {
return null;
}
const parsedScriptConfig: Record<string, unknown> = {};
Object.entries(scriptConfig).forEach(([name, value]) => {
if (typeof value === "string") {
value = value.replace("{TRACKING_ID}", trackingId);
}
parsedScriptConfig[name] = value;
});
return <Script key={appId} {...parsedScriptConfig} defer />;
const parseValue = <T extends string|undefined, >(val: T ):T => val ? val.replace(/\{TRACKING_ID\}/g, trackingId) as T : val
return scriptConfig.scripts.map((script, index)=>{
const parsedAttributes: NonNullable<AppScript["attrs"]> = {};
const attrs = script.attrs || {};
Object.entries(attrs).forEach(([name, value]) => {
if (typeof value === "string") {
value = parseValue(value)
}
parsedAttributes[name] = value;
});
return <Script src={parseValue(script.src)} key={`${appId}-${index}`} {...{dangerouslySetInnerHTML:{
__html: parseValue(script.content) || ""
}, ...parsedAttributes}} defer />;
})
})}
</>
);

View File

@ -34,6 +34,7 @@ export const AppSettingsComponentsMap = {
};
export const EventTypeAddonMap = {
fathom: dynamic(() => import("./fathom/extensions/EventTypeAppCard")),
ga4: dynamic(() => import("./ga4/extensions/EventTypeAppCard")),
giphy: dynamic(() => import("./giphy/extensions/EventTypeAppCard")),
qr_code: dynamic(() => import("./qr_code/extensions/EventTypeAppCard")),
rainbow: dynamic(() => import("./rainbow/extensions/EventTypeAppCard")),

View File

@ -13,6 +13,7 @@ import { metadata as exchange2013calendar_meta } from "./exchange2013calendar/_m
import { metadata as exchange2016calendar_meta } from "./exchange2016calendar/_metadata";
import { metadata as exchangecalendar_meta } from "./exchangecalendar/_metadata";
import { metadata as fathom_meta } from "./fathom/_metadata";
import { metadata as ga4_meta } from "./ga4/_metadata";
import { metadata as giphy_meta } from "./giphy/_metadata";
import { metadata as googlecalendar_meta } from "./googlecalendar/_metadata";
import { metadata as googlevideo_meta } from "./googlevideo/_metadata";
@ -52,6 +53,7 @@ export const appStoreMetadata = {
exchange2016calendar: exchange2016calendar_meta,
exchangecalendar: exchangecalendar_meta,
fathom: fathom_meta,
ga4: ga4_meta,
giphy: giphy_meta,
googlecalendar: googlecalendar_meta,
googlevideo: googlevideo_meta,

View File

@ -4,6 +4,7 @@
**/
import { appDataSchema as routing_forms_schema } from "./ee/routing-forms/zod";
import { appDataSchema as fathom_schema } from "./fathom/zod";
import { appDataSchema as ga4_schema } from "./ga4/zod";
import { appDataSchema as giphy_schema } from "./giphy/zod";
import { appDataSchema as qr_code_schema } from "./qr_code/zod";
import { appDataSchema as rainbow_schema } from "./rainbow/zod";
@ -12,6 +13,7 @@ import { appDataSchema as stripepayment_schema } from "./stripepayment/zod";
export const appDataSchemas = {
"routing-forms": routing_forms_schema,
fathom: fathom_schema,
ga4: ga4_schema,
giphy: giphy_schema,
qr_code: qr_code_schema,
rainbow: rainbow_schema,

View File

@ -13,6 +13,7 @@ export const apiHandlers = {
exchange2016calendar: import("./exchange2016calendar/api"),
exchangecalendar: import("./exchangecalendar/api"),
fathom: import("./fathom/api"),
ga4: import("./ga4/api"),
giphy: import("./giphy/api"),
googlecalendar: import("./googlecalendar/api"),
hubspotothercalendar: import("./hubspotothercalendar/api"),

View File

@ -0,0 +1,42 @@
import { AppScript } from "./BookingPageTagManager";
import { appDataSchemas } from "./apps.schemas.generated";
// TODO: This config might be imported from {APP}/eventTypeAnalytics.ts.
export const trackingApps: Partial<
Record<
keyof typeof appDataSchemas,
{
scripts: AppScript[];
}
>
> = {
fathom: {
scripts: [
{
src: "https://cdn.usefathom.com/script.js",
content: undefined,
attrs: {
"data-site": "{TRACKING_ID}",
},
},
],
},
ga4: {
scripts: [
{
src: "https://www.googletagmanager.com/gtag/js?id={TRACKING_ID}",
content: undefined,
attrs: {},
},
{
src: undefined,
content: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{TRACKING_ID}');
`,
},
],
},
};

View File

@ -3,12 +3,11 @@ import { useState } from "react";
import { useAppContextWithSchema } from "@calcom/app-store/EventTypeAppContext";
import AppCard from "@calcom/app-store/_components/AppCard";
import type { EventTypeAppCardComponent } from "@calcom/app-store/types";
import { Icon } from "@calcom/ui";
import { Input, TextField } from "@calcom/ui/v2";
import { TextField } from "@calcom/ui/v2";
import { appDataSchema } from "../zod";
const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ eventType, app }) {
const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app }) {
const [getAppData, setAppData] = useAppContextWithSchema<typeof appDataSchema>();
const trackingId = getAppData("trackingId");
const [enabled, setEnabled] = useState(getAppData("enabled"));

View File

@ -0,0 +1,14 @@
---
description: Google Analytics is a web analytics service offered by Google that tracks and reports website traffic, currently as a platform inside the Google Marketing Platform brand.
items:
- /api/app-store/ga4/1.jpeg
- /api/app-store/ga4/2.jpeg
- /api/app-store/ga4/3.jpeg
- /api/app-store/ga4/4.jpeg
- /api/app-store/ga4/5.jpeg
---
<Slider items={items} />
{description}

View File

@ -0,0 +1,10 @@
import type { AppMeta } from "@calcom/types/App";
import config from "./config.json";
export const metadata = {
category: "other",
...config,
} as AppMeta;
export default metadata;

View File

@ -0,0 +1,17 @@
import { AppDeclarativeHandler } from "@calcom/types/AppHandler";
import { createDefaultInstallation } from "../../_utils/installation";
import appConfig from "../config.json";
const handler: AppDeclarativeHandler = {
// Instead of passing appType and slug from here, api/integrations/[..args] should be able to derive and pass these directly to createCredential
appType: appConfig.type,
variant: appConfig.variant,
slug: appConfig.slug,
supportsMultipleInstalls: false,
handlerType: "add",
createCredential: ({ appType, user, slug }) =>
createDefaultInstallation({ appType, userId: user.id, slug, key: {} }),
};
export default handler;

View File

@ -0,0 +1 @@
export { default as add } from "./add";

View File

@ -0,0 +1,16 @@
{
"/*": "Don't modify slug - If required, do it using cli edit command",
"name": "Google Analytics",
"slug": "ga4",
"type": "ga4_analytics",
"imageSrc": "/api/app-store/ga4/icon.svg",
"logo": "/api/app-store/ga4/icon.svg",
"url": "https://marketingplatform.google.com",
"variant": "analytics",
"categories": ["analytics"],
"publisher": "Cal.com, Inc.",
"email": "support@cal.com",
"description": "Google Analytics is a web analytics service offered by Google that tracks and reports website traffic, currently as a platform inside the Google Marketing Platform brand.",
"extendsFeature": "EventType",
"__createdUsingCli": true
}

View File

@ -0,0 +1,38 @@
import { useState } from "react";
import { useAppContextWithSchema } from "@calcom/app-store/EventTypeAppContext";
import AppCard from "@calcom/app-store/_components/AppCard";
import type { EventTypeAppCardComponent } from "@calcom/app-store/types";
import { TextField } from "@calcom/ui/v2";
import { appDataSchema } from "../zod";
const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app }) {
const [getAppData, setAppData] = useAppContextWithSchema<typeof appDataSchema>();
const trackingId = getAppData("trackingId");
const [enabled, setEnabled] = useState(getAppData("enabled"));
return (
<AppCard
setAppData={setAppData}
app={app}
switchOnClick={(e) => {
if (!e) {
setEnabled(false);
} else {
setEnabled(true);
}
}}
switchChecked={enabled}>
<TextField
name="Tracking ID"
value={trackingId}
onChange={(e) => {
setAppData("trackingId", e.target.value);
}}
/>
</AppCard>
);
};
export default EventTypeAppCard;

View File

@ -0,0 +1,2 @@
export * as api from "./api";
export { metadata } from "./_metadata";

View File

@ -0,0 +1,14 @@
{
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"name": "@calcom/ga4",
"version": "0.0.0",
"main": "./index.ts",
"description": "Google Analytics is a web analytics service offered by Google that tracks and reports website traffic, currently as a platform inside the Google Marketing Platform brand.",
"dependencies": {
"@calcom/lib": "*"
},
"devDependencies": {
"@calcom/types": "*"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@ -0,0 +1,35 @@
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1_8)">
<path d="M91.9072 0H73.6981C69.2937 0 65.6782 3.61842 65.6782 8.02632V31.5789H39.3177C35.0448 31.5789 31.5608 35.0658 31.5608 39.4079V65.7895H7.82976C3.49113 65.7895 0.00708008 69.2763 0.00708008 73.6184V92.0395C0.00708008 96.3816 3.49113 99.8684 7.82976 100H91.9729C96.3773 100 99.9928 96.3816 99.9928 91.9737V8.02632C99.9271 3.61842 96.3115 0 91.9072 0Z" fill="url(#paint0_linear_1_8)"/>
<path d="M91.9072 0H73.6981C69.2937 0 65.6782 3.61842 65.6782 8.02632V31.5789H39.3177C35.0448 31.5789 31.5608 35.0658 31.5608 39.4079V65.7895H7.82976C3.49113 65.7895 0.00708008 69.2763 0.00708008 73.6184V92.0395C0.00708008 96.3816 3.49113 99.8684 7.82976 100H91.9729C96.3773 100 99.9928 96.3816 99.9928 91.9737V8.02632C99.9271 3.61842 96.3115 0 91.9072 0Z" fill="url(#paint1_linear_1_8)"/>
<path d="M91.9072 0H73.8296C69.4252 0 65.8097 3.61842 65.8097 8.02632V100H91.9072C96.3116 100 99.9271 96.3816 99.9271 91.9737V8.02632C99.9271 3.61842 96.3116 0 91.9072 0Z" fill="#F57C00"/>
<path d="M31.5608 39.4736V65.7894H7.8955C3.55687 65.7894 0.00708008 69.342 0.00708008 73.6841V92.1052C0.00708008 96.4473 3.55687 99.9999 7.8955 99.9999H65.7439V31.5789H39.4492C35.1106 31.5789 31.5608 35.1315 31.5608 39.4736Z" fill="#FFC107"/>
<path d="M65.744 31.579V100H91.8415C96.2459 100 99.8614 96.3816 99.8614 91.9737V65.7895L65.744 31.579Z" fill="url(#paint2_linear_1_8)"/>
<path opacity="0.2" d="M39.4492 32.2369H65.7439V31.579H39.4492C35.1106 31.579 31.5608 35.1316 31.5608 39.4737V40.1316C31.5608 35.7895 35.1106 32.2369 39.4492 32.2369Z" fill="white"/>
<path opacity="0.2" d="M7.8955 66.4473H31.5608V65.7894H7.8955C3.55687 65.7894 0.00708008 69.3421 0.00708008 73.6842V74.3421C0.00708008 70 3.55687 66.4473 7.8955 66.4473Z" fill="white"/>
<path opacity="0.2" d="M91.9072 0H73.8296C69.4252 0 65.8097 3.61842 65.8097 8.02631V8.68421C65.8097 4.27632 69.4252 0.657895 73.8296 0.657895H91.9072C96.3116 0.657895 99.9271 4.27632 99.9271 8.68421V8.02631C99.9271 3.61842 96.3116 0 91.9072 0Z" fill="white"/>
<path opacity="0.2" d="M91.9072 99.3421H7.8955C3.55687 99.3421 0.00708008 95.7895 0.00708008 91.4474V92.1053C0.00708008 96.4474 3.55687 100 7.8955 100H91.8414C96.2458 100 99.8613 96.3816 99.8613 91.9737V91.3158C99.9271 95.7237 96.3115 99.3421 91.9072 99.3421V99.3421Z" fill="#BF360C"/>
<path d="M91.9072 0H73.8296C69.4252 0 65.8097 3.61842 65.8097 8.02632V31.5789H39.5149C35.1763 31.5789 31.6265 35.1316 31.6265 39.4737V65.7895H7.8955C3.55687 65.7895 0.00708008 69.3421 0.00708008 73.6842V92.1053C0.00708008 96.4474 3.55687 100 7.8955 100H91.9072C96.3116 100 99.9271 96.3816 99.9271 91.9737V8.02632C99.9271 3.61842 96.3116 0 91.9072 0Z" fill="url(#paint3_linear_1_8)"/>
</g>
<defs>
<linearGradient id="paint0_linear_1_8" x1="0.00708008" y1="50" x2="99.9928" y2="50" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.1"/>
<stop offset="1" stop-color="white" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint1_linear_1_8" x1="0.00708008" y1="50" x2="99.9928" y2="50" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.1"/>
<stop offset="1" stop-color="white" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint2_linear_1_8" x1="48.8168" y1="48.8487" x2="97.5733" y2="97.5663" gradientUnits="userSpaceOnUse">
<stop stop-color="#BF360C" stop-opacity="0.2"/>
<stop offset="1" stop-color="#BF360C" stop-opacity="0.02"/>
</linearGradient>
<linearGradient id="paint3_linear_1_8" x1="33.5125" y1="33.573" x2="98.2006" y2="98.2094" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.1"/>
<stop offset="1" stop-color="white" stop-opacity="0"/>
</linearGradient>
<clipPath id="clip0_1_8">
<rect width="100" height="100" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,9 @@
import { z } from "zod";
import { eventTypeAppCardZod } from "../eventTypeAppCardZod";
export const appDataSchema = eventTypeAppCardZod.merge(
z.object({
trackingId: z.string(),
})
);

View File

@ -84,6 +84,12 @@
"slug": "fathom",
"type": "fathom_analytics"
},
{
"dirName": "ga4",
"categories": ["analytics"],
"slug": "ga4",
"type": "ga4_analytics"
},
{
"dirName": "pipedream",
"categories": ["automation"],