From 2894be86892eab4292a04db4fdf119512062c4b2 Mon Sep 17 00:00:00 2001 From: vklimontovich Date: Tue, 27 Apr 2021 17:19:12 +0300 Subject: [PATCH] Added telemetry collection (through jitsu.com) - Introduced useTelemetry() hook - Telemetry events are sent for page_view, booking_confirmed, time_selected, date_selected events - Telemetry is configured (and can be disabled) with NEXT_PUBLIC_TELEMETRY_KEY env variable --- .env.example | 5 +++- components/Shell.tsx | 10 ++++++- lib/telemetry.ts | 62 +++++++++++++++++++++++++++++++++++++++++ package.json | 1 + pages/[user]/[type].tsx | 7 ++++- pages/[user]/book.tsx | 7 +++++ pages/_app.tsx | 9 ++++-- yarn.lock | 5 ++++ 8 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 lib/telemetry.ts diff --git a/.env.example b/.env.example index 269c048b..9fa0bef4 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,10 @@ -DATABASE_URL='postgresql://:@:' +DATABASE_URL='postgresql://:@:/' GOOGLE_API_CREDENTIALS='secret' NEXTAUTH_URL='http://localhost:3000' +# Remove this var if you don't want Calendso to collect anonymous usage +NEXT_PUBLIC_TELEMETRY_KEY=js.2pvs2bbpqq1zxna97wcml.oi2jzirnbj1ev4tc57c5r + # Used for the Office 365 / Outlook.com Calendar integration MS_GRAPH_CLIENT_ID= MS_GRAPH_CLIENT_SECRET= \ No newline at end of file diff --git a/components/Shell.tsx b/components/Shell.tsx index bafbd386..e0a8fe0f 100644 --- a/components/Shell.tsx +++ b/components/Shell.tsx @@ -1,14 +1,22 @@ import Link from 'next/link'; -import { useState } from "react"; +import {useContext, useEffect, useState} from "react"; import { useRouter } from "next/router"; import { signOut, useSession } from 'next-auth/client'; import { MenuIcon, XIcon } from '@heroicons/react/outline'; +import {TelemetryContext, useTelemetry} from "../lib/telemetry"; export default function Shell(props) { const router = useRouter(); const [ session, loading ] = useSession(); const [ profileDropdownExpanded, setProfileDropdownExpanded ] = useState(false); const [ mobileMenuExpanded, setMobileMenuExpanded ] = useState(false); + let telemetry = useTelemetry(); + + useEffect(() => { + telemetry.withJitsu((jitsu) => { + return jitsu.track('page_view', {page_url: router.pathname}) + }); + }, [telemetry]) const toggleProfileDropdown = () => { setProfileDropdownExpanded(!profileDropdownExpanded); diff --git a/lib/telemetry.ts b/lib/telemetry.ts new file mode 100644 index 00000000..5cc38163 --- /dev/null +++ b/lib/telemetry.ts @@ -0,0 +1,62 @@ +import React, {useContext} from 'react' +import {jitsuClient, JitsuClient} from "@jitsu/sdk-js"; + + +/** + * Telemetry client + */ +export type TelemetryClient = { + /** + * Use it as: withJitsu((jitsu) => {return jitsu.track()}). If telemetry is disabled, the callback will ignored + * + * ATTENTION: always return the value of jitsu.track() or id() call. Otherwise unhandled rejection can happen, + * which is handled in Next.js with a popup. + */ + withJitsu: (callback: (jitsu: JitsuClient) => void | Promise) => void +} + +const emptyClient: TelemetryClient = {withJitsu: () => {}}; + +function useTelemetry(): TelemetryClient { + return useContext(TelemetryContext); +} + +function createTelemetryClient(): TelemetryClient { + if (process.env.NEXT_PUBLIC_TELEMETRY_KEY) { + return { + withJitsu: (callback) => { + if (!process.env.NEXT_PUBLIC_TELEMETRY_KEY) { + //telemetry is disabled + return; + } + if (!window) { + console.warn("Jitsu has been called during SSR, this scenario isn't supported yet"); + return; + } else if (!window['jitsu']) { + window['jitsu'] = jitsuClient({ + log_level: 'ERROR', + tracking_host: "https://t.calendso.com", + key: "js.2pvs2bbpqq1zxna97wcml.oi2jzirnbj1ev4tc57c5r", + cookie_name: "__clnds", + capture_3rd_party_cookies: false, + }); + } + let res = callback(window['jitsu']); + if (res && typeof res['catch'] === "function") { + res.catch(e => { + console.debug("Unable to send telemetry event", e) + }); + } + } + } + } else { + return emptyClient; + } +} + + +const TelemetryContext = React.createContext(emptyClient) + +const TelemetryProvider = TelemetryContext.Provider + +export { TelemetryContext, TelemetryProvider, createTelemetryClient, useTelemetry }; diff --git a/package.json b/package.json index cc4b0df1..d5e522b4 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dependencies": { "@headlessui/react": "^1.0.0", "@heroicons/react": "^1.0.1", + "@jitsu/sdk-js": "^2.0.0", "@prisma/client": "2.21.2", "@tailwindcss/forms": "^0.2.1", "bcryptjs": "^2.4.3", diff --git a/pages/[user]/[type].tsx b/pages/[user]/[type].tsx index 1b9392e7..a49ba804 100644 --- a/pages/[user]/[type].tsx +++ b/pages/[user]/[type].tsx @@ -16,6 +16,7 @@ dayjs.extend(utc); dayjs.extend(timezone); import getSlots from '../../lib/slots'; +import {useTelemetry} from "../../lib/telemetry"; function classNames(...classes) { return classes.filter(Boolean).join(' ') @@ -29,6 +30,7 @@ export default function Type(props) { const [isTimeOptionsOpen, setIsTimeOptionsOpen] = useState(false); const [is24h, setIs24h] = useState(false); const [busy, setBusy] = useState([]); + const telemetry = useTelemetry(); // Get router variables const router = useRouter(); @@ -68,7 +70,10 @@ export default function Type(props) { } const calendar = days.map((day) => - ); diff --git a/pages/[user]/book.tsx b/pages/[user]/book.tsx index 537c0437..8fcc835c 100644 --- a/pages/[user]/book.tsx +++ b/pages/[user]/book.tsx @@ -3,14 +3,21 @@ import Link from 'next/link'; import { useRouter } from 'next/router'; import { ClockIcon, CalendarIcon } from '@heroicons/react/solid'; import prisma from '../../lib/prisma'; +import {useTelemetry} from "../../lib/telemetry"; +import {useEffect} from "react"; const dayjs = require('dayjs'); export default function Book(props) { const router = useRouter(); const { date, user } = router.query; + const telemetry = useTelemetry(); + useEffect(() => { + telemetry.withJitsu(jitsu => jitsu.track('time_selected')); + }) const bookingHandler = event => { event.preventDefault(); + telemetry.withJitsu(jitsu => jitsu.track('booking_confirmed')); const res = fetch( '/api/book/' + user, { diff --git a/pages/_app.tsx b/pages/_app.tsx index 2bbf669d..e2964dde 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,11 +1,14 @@ import '../styles/globals.css'; +import {createTelemetryClient, TelemetryProvider} from '../lib/telemetry'; import { Provider } from 'next-auth/client'; function MyApp({ Component, pageProps }) { return ( - - - + + + + + ); } diff --git a/yarn.lock b/yarn.lock index 6921863c..6cbce375 100644 --- a/yarn.lock +++ b/yarn.lock @@ -160,6 +160,11 @@ resolved "https://registry.npmjs.org/@heroicons/react/-/react-1.0.1.tgz" integrity sha512-uikw2gKCmqnvjVxitecWfFLMOKyL9BTFcU4VM3hHj9OMwpkCr5Ke+MRMyY2/aQVmsYs4VTq7NCFX05MYwAHi3g== +"@jitsu/sdk-js@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@jitsu/sdk-js/-/sdk-js-2.0.0.tgz#01ef96c602b3b2aa1e1a4bf87e868f7a5bfe3b35" + integrity sha512-+IQLEbzrIpuXKmP2bLbD7eAdF1WaEcZ2eaSMl6AAwcE0BEFctjNG8QhQYWsMgb+KahNKFz1ARll3aJegkqgrew== + "@next/env@10.0.8": version "10.0.8" resolved "https://registry.npmjs.org/@next/env/-/env-10.0.8.tgz"