Merge pull request #110 from vklimontovich/main
Added telemetry collection (through jitsu.com)
This commit is contained in:
commit
689b4fa32b
8 changed files with 100 additions and 7 deletions
|
@ -1,7 +1,10 @@
|
||||||
DATABASE_URL='postgresql://<user>:<pass>@<db-host>:<db-port>'
|
DATABASE_URL='postgresql://<user>:<pass>@<db-host>:<db-port>/<db-name>'
|
||||||
GOOGLE_API_CREDENTIALS='secret'
|
GOOGLE_API_CREDENTIALS='secret'
|
||||||
NEXTAUTH_URL='http://localhost:3000'
|
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
|
# Used for the Office 365 / Outlook.com Calendar integration
|
||||||
MS_GRAPH_CLIENT_ID=
|
MS_GRAPH_CLIENT_ID=
|
||||||
MS_GRAPH_CLIENT_SECRET=
|
MS_GRAPH_CLIENT_SECRET=
|
|
@ -1,14 +1,22 @@
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useState } from "react";
|
import {useContext, useEffect, useState} from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { signOut, useSession } from 'next-auth/client';
|
import { signOut, useSession } from 'next-auth/client';
|
||||||
import { MenuIcon, XIcon } from '@heroicons/react/outline';
|
import { MenuIcon, XIcon } from '@heroicons/react/outline';
|
||||||
|
import {TelemetryContext, useTelemetry} from "../lib/telemetry";
|
||||||
|
|
||||||
export default function Shell(props) {
|
export default function Shell(props) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [ session, loading ] = useSession();
|
const [ session, loading ] = useSession();
|
||||||
const [ profileDropdownExpanded, setProfileDropdownExpanded ] = useState(false);
|
const [ profileDropdownExpanded, setProfileDropdownExpanded ] = useState(false);
|
||||||
const [ mobileMenuExpanded, setMobileMenuExpanded ] = 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 = () => {
|
const toggleProfileDropdown = () => {
|
||||||
setProfileDropdownExpanded(!profileDropdownExpanded);
|
setProfileDropdownExpanded(!profileDropdownExpanded);
|
||||||
|
|
62
lib/telemetry.ts
Normal file
62
lib/telemetry.ts
Normal file
|
@ -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>) => 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<TelemetryClient>(emptyClient)
|
||||||
|
|
||||||
|
const TelemetryProvider = TelemetryContext.Provider
|
||||||
|
|
||||||
|
export { TelemetryContext, TelemetryProvider, createTelemetryClient, useTelemetry };
|
|
@ -11,6 +11,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^1.0.0",
|
"@headlessui/react": "^1.0.0",
|
||||||
"@heroicons/react": "^1.0.1",
|
"@heroicons/react": "^1.0.1",
|
||||||
|
"@jitsu/sdk-js": "^2.0.0",
|
||||||
"@prisma/client": "2.21.2",
|
"@prisma/client": "2.21.2",
|
||||||
"@tailwindcss/forms": "^0.2.1",
|
"@tailwindcss/forms": "^0.2.1",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
|
|
|
@ -16,6 +16,7 @@ dayjs.extend(utc);
|
||||||
dayjs.extend(timezone);
|
dayjs.extend(timezone);
|
||||||
|
|
||||||
import getSlots from '../../lib/slots';
|
import getSlots from '../../lib/slots';
|
||||||
|
import {useTelemetry} from "../../lib/telemetry";
|
||||||
|
|
||||||
function classNames(...classes) {
|
function classNames(...classes) {
|
||||||
return classes.filter(Boolean).join(' ')
|
return classes.filter(Boolean).join(' ')
|
||||||
|
@ -29,6 +30,7 @@ export default function Type(props) {
|
||||||
const [isTimeOptionsOpen, setIsTimeOptionsOpen] = useState(false);
|
const [isTimeOptionsOpen, setIsTimeOptionsOpen] = useState(false);
|
||||||
const [is24h, setIs24h] = useState(false);
|
const [is24h, setIs24h] = useState(false);
|
||||||
const [busy, setBusy] = useState([]);
|
const [busy, setBusy] = useState([]);
|
||||||
|
const telemetry = useTelemetry();
|
||||||
|
|
||||||
// Get router variables
|
// Get router variables
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -67,7 +69,6 @@ export default function Type(props) {
|
||||||
days.push(i);
|
days.push(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Create placeholder elements for empty days in first week
|
// Create placeholder elements for empty days in first week
|
||||||
const weekdayOfFirst = dayjs().month(selectedMonth).date(1).day();
|
const weekdayOfFirst = dayjs().month(selectedMonth).date(1).day();
|
||||||
const emptyDays = Array(weekdayOfFirst).fill(null).map((day, i) =>
|
const emptyDays = Array(weekdayOfFirst).fill(null).map((day, i) =>
|
||||||
|
@ -78,7 +79,10 @@ export default function Type(props) {
|
||||||
|
|
||||||
// Combine placeholder days with actual days
|
// Combine placeholder days with actual days
|
||||||
const calendar = [...emptyDays, ...days.map((day) =>
|
const calendar = [...emptyDays, ...days.map((day) =>
|
||||||
<button key={day} onClick={(e) => setSelectedDate(dayjs().tz(dayjs.tz.guess()).month(selectedMonth).date(day))} disabled={selectedMonth < dayjs().format('MM') && dayjs().month(selectedMonth).format("D") > day} className={"text-center w-10 h-10 rounded-full mx-auto " + (dayjs().isSameOrBefore(dayjs().date(day).month(selectedMonth)) ? 'bg-blue-50 text-blue-600 font-medium' : 'text-gray-400 font-light') + (dayjs(selectedDate).month(selectedMonth).format("D") == day ? ' bg-blue-600 text-white-important' : '')}>
|
<button key={day} onClick={(e) => {
|
||||||
|
telemetry.withJitsu((jitsu) => jitsu.track('date_selected'))
|
||||||
|
setSelectedDate(dayjs().tz(dayjs.tz.guess()).month(selectedMonth).date(day))
|
||||||
|
}} disabled={selectedMonth < dayjs().format('MM') && dayjs().month(selectedMonth).format("D") > day} className={"text-center w-10 h-10 rounded-full mx-auto " + (dayjs().isSameOrBefore(dayjs().date(day).month(selectedMonth)) ? 'bg-blue-50 text-blue-600 font-medium' : 'text-gray-400 font-light') + (dayjs(selectedDate).month(selectedMonth).format("D") == day ? ' bg-blue-600 text-white-important' : '')}>
|
||||||
{day}
|
{day}
|
||||||
</button>
|
</button>
|
||||||
)];
|
)];
|
||||||
|
|
|
@ -3,14 +3,21 @@ import Link from 'next/link';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { ClockIcon, CalendarIcon } from '@heroicons/react/solid';
|
import { ClockIcon, CalendarIcon } from '@heroicons/react/solid';
|
||||||
import prisma from '../../lib/prisma';
|
import prisma from '../../lib/prisma';
|
||||||
|
import {useTelemetry} from "../../lib/telemetry";
|
||||||
|
import {useEffect} from "react";
|
||||||
const dayjs = require('dayjs');
|
const dayjs = require('dayjs');
|
||||||
|
|
||||||
export default function Book(props) {
|
export default function Book(props) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { date, user } = router.query;
|
const { date, user } = router.query;
|
||||||
|
const telemetry = useTelemetry();
|
||||||
|
useEffect(() => {
|
||||||
|
telemetry.withJitsu(jitsu => jitsu.track('time_selected'));
|
||||||
|
})
|
||||||
|
|
||||||
const bookingHandler = event => {
|
const bookingHandler = event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
telemetry.withJitsu(jitsu => jitsu.track('booking_confirmed'));
|
||||||
const res = fetch(
|
const res = fetch(
|
||||||
'/api/book/' + user,
|
'/api/book/' + user,
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import '../styles/globals.css';
|
import '../styles/globals.css';
|
||||||
|
import {createTelemetryClient, TelemetryProvider} from '../lib/telemetry';
|
||||||
import { Provider } from 'next-auth/client';
|
import { Provider } from 'next-auth/client';
|
||||||
|
|
||||||
function MyApp({ Component, pageProps }) {
|
function MyApp({ Component, pageProps }) {
|
||||||
return (
|
return (
|
||||||
|
<TelemetryProvider value={createTelemetryClient()}>
|
||||||
<Provider session={pageProps.session}>
|
<Provider session={pageProps.session}>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</Provider>
|
</Provider>
|
||||||
|
</TelemetryProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -160,6 +160,11 @@
|
||||||
resolved "https://registry.npmjs.org/@heroicons/react/-/react-1.0.1.tgz"
|
resolved "https://registry.npmjs.org/@heroicons/react/-/react-1.0.1.tgz"
|
||||||
integrity sha512-uikw2gKCmqnvjVxitecWfFLMOKyL9BTFcU4VM3hHj9OMwpkCr5Ke+MRMyY2/aQVmsYs4VTq7NCFX05MYwAHi3g==
|
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.2.0":
|
"@next/env@10.2.0":
|
||||||
version "10.2.0"
|
version "10.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@next/env/-/env-10.2.0.tgz#154dbce2efa3ad067ebd20b7d0aa9aed775e7c97"
|
resolved "https://registry.yarnpkg.com/@next/env/-/env-10.2.0.tgz#154dbce2efa3ad067ebd20b7d0aa9aed775e7c97"
|
||||||
|
|
Loading…
Reference in a new issue