From 1ed68a718fb42bef60be791c6dccee1a855fc014 Mon Sep 17 00:00:00 2001 From: Germain Date: Thu, 28 Apr 2022 11:46:02 +0100 Subject: [PATCH] Add new user signup event tracking in PostHog (#8412) --- package.json | 2 +- src/MatrixClientPeg.ts | 9 ++-- src/PosthogAnalytics.ts | 51 ++++++++++++++++--- .../views/auth/RegistrationForm.tsx | 3 ++ src/components/views/elements/SSOButtons.tsx | 24 +++++++++ yarn.lock | 4 +- 6 files changed, 79 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index c62184810c..b2cbf76a4d 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "linkifyjs": "^4.0.0-beta.4", "lodash": "^4.17.20", "maplibre-gl": "^1.15.2", - "matrix-analytics-events": "github:matrix-org/matrix-analytics-events.git#4aef17b56798639906f26a8739043a3c5c5fde7e", + "matrix-analytics-events": "github:matrix-org/matrix-analytics-events.git#761751d7a96c129f118f1e3a89f27e77bbfddf9e", "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "^0.0.1-beta.7", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index 0ef270051c..46d599b156 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -72,11 +72,11 @@ export interface IMatrixClientPeg { * If we've registered a user ID we set this to the ID of the * user we've just registered. If they then go & log in, we * can send them to the welcome user (obviously this doesn't - * guarentee they'll get a chat with the welcome user). + * guarantee they'll get a chat with the welcome user). * * @param {string} uid The user ID of the user we've just registered */ - setJustRegisteredUserId(uid: string): void; + setJustRegisteredUserId(uid: string | null): void; /** * Returns true if the current user has just been registered by this @@ -139,7 +139,8 @@ class MatrixClientPegClass implements IMatrixClientPeg { public setJustRegisteredUserId(uid: string | null): void { this.justRegisteredUserId = uid; if (uid) { - window.localStorage.setItem("mx_registration_time", String(new Date().getTime())); + const registrationTime = Date.now().toString(); + window.localStorage.setItem("mx_registration_time", registrationTime); } } @@ -156,7 +157,7 @@ class MatrixClientPegClass implements IMatrixClientPeg { } try { - const registrationTime = parseInt(window.localStorage.getItem("mx_registration_time")); + const registrationTime = parseInt(window.localStorage.getItem("mx_registration_time"), 10); const diff = Date.now() - registrationTime; return (diff / 36e5) <= hours; } catch (e) { diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts index 01a8f52d8a..3ce8707d1c 100644 --- a/src/PosthogAnalytics.ts +++ b/src/PosthogAnalytics.ts @@ -18,6 +18,7 @@ import posthog, { PostHog } from 'posthog-js'; import { MatrixClient } from "matrix-js-sdk/src/client"; import { logger } from "matrix-js-sdk/src/logger"; import { UserProperties } from "matrix-analytics-events/types/typescript/UserProperties"; +import { Signup } from 'matrix-analytics-events/types/typescript/Signup'; import PlatformPeg from './PlatformPeg'; import SdkConfig from './SdkConfig'; @@ -50,6 +51,10 @@ export interface IPosthogEvent { "$set_once"?: void; } +export interface IPostHogEventOptions { + timestamp?: Date; +} + export enum Anonymity { Disabled, Anonymous, @@ -117,6 +122,7 @@ export class PosthogAnalytics { private static ANALYTICS_EVENT_TYPE = "im.vector.analytics"; private propertiesForNextEvent: Partial> = {}; private userPropertyCache: UserProperties = {}; + private authenticationType: Signup["authenticationType"] = "Other"; public static get instance(): PosthogAnalytics { if (!this._instance) { @@ -200,16 +206,20 @@ export class PosthogAnalytics { }; } - private capture(eventName: string, properties: posthog.Properties) { + // eslint-disable-nextline no-unused-varsx + private capture(eventName: string, properties: posthog.Properties, options?: IPostHogEventOptions) { if (!this.enabled) { return; } const { origin, hash, pathname } = window.location; properties["redactedCurrentUrl"] = getRedactedCurrentLocation(origin, hash, pathname); - this.posthog.capture(eventName, { - ...this.propertiesForNextEvent, - ...properties, - }); + this.posthog.capture( + eventName, + { ...this.propertiesForNextEvent, ...properties }, + // TODO: Uncomment below once https://github.com/PostHog/posthog-js/pull/391 + // gets merged + /* options as any, */ // No proper type definition in the posthog library + ); this.propertiesForNextEvent = {}; } @@ -272,9 +282,12 @@ export class PosthogAnalytics { this.setAnonymity(Anonymity.Disabled); } - public trackEvent({ eventName, ...properties }: E): void { + public trackEvent( + { eventName, ...properties }: E, + options?: IPostHogEventOptions, + ): void { if (this.anonymity == Anonymity.Disabled || this.anonymity == Anonymity.Anonymous) return; - this.capture(eventName, properties); + this.capture(eventName, properties, options); } public setProperty(key: K, value: UserProperties[K]): void { @@ -313,6 +326,9 @@ export class PosthogAnalytics { this.setAnonymity(anonymity); if (anonymity === Anonymity.Pseudonymous) { await this.identifyUser(MatrixClientPeg.get(), PosthogAnalytics.getRandomAnalyticsId); + if (MatrixClientPeg.currentUserIsJustRegistered()) { + this.trackNewUserEvent(); + } } if (anonymity !== Anonymity.Disabled) { @@ -334,4 +350,25 @@ export class PosthogAnalytics { this.updateAnonymityFromSettings(!!newValue); }); } + + public setAuthenticationType(authenticationType: Signup["authenticationType"]): void { + this.authenticationType = authenticationType; + } + + private trackNewUserEvent(): void { + // This is the only event that could have occured before analytics opt-in + // that we want to accumulate before the user has given consent + // All other scenarios should not track a user before they have given + // explicit consent that they are ok with their analytics data being collected + const options: IPostHogEventOptions = {}; + const registrationTime = parseInt(window.localStorage.getItem("mx_registration_time"), 10); + if (!isNaN(registrationTime)) { + options.timestamp = new Date(registrationTime); + } + + return this.trackEvent({ + eventName: "Signup", + authenticationType: this.authenticationType, + }, options); + } } diff --git a/src/components/views/auth/RegistrationForm.tsx b/src/components/views/auth/RegistrationForm.tsx index b57ddf5009..83ee70a705 100644 --- a/src/components/views/auth/RegistrationForm.tsx +++ b/src/components/views/auth/RegistrationForm.tsx @@ -33,6 +33,7 @@ import Field from '../elements/Field'; import RegistrationEmailPromptDialog from '../dialogs/RegistrationEmailPromptDialog'; import CountryDropdown from "./CountryDropdown"; import PassphraseConfirmField from "./PassphraseConfirmField"; +import { PosthogAnalytics } from '../../../PosthogAnalytics'; enum RegistrationField { Email = "field_email", @@ -147,6 +148,8 @@ export default class RegistrationForm extends React.PureComponent { idp: IIdentityProvider; @@ -50,6 +52,26 @@ const getIcon = (brand: IdentityProviderBrand | string) => { } }; +const getAuthenticationType = (brand: IdentityProviderBrand | string): Signup["authenticationType"] => { + switch (brand) { + case IdentityProviderBrand.Apple: + return "Apple"; + case IdentityProviderBrand.Facebook: + return "Facebook"; + case IdentityProviderBrand.Github: + return "GitHub"; + case IdentityProviderBrand.Gitlab: + return "GitLab"; + case IdentityProviderBrand.Google: + return "Google"; + // Not supported on the analytics SDK at the moment. + // case IdentityProviderBrand.Twitter: + // return "Twitter"; + default: + return "SSO"; + } +}; + const SSOButton: React.FC = ({ matrixClient, loginType, @@ -62,6 +84,8 @@ const SSOButton: React.FC = ({ const label = idp ? _t("Continue with %(provider)s", { provider: idp.name }) : _t("Sign in with single sign-on"); const onClick = () => { + const authenticationType = getAuthenticationType(idp.brand); + PosthogAnalytics.instance.setAuthenticationType(authenticationType); PlatformPeg.get().startSingleSignOn(matrixClient, loginType, fragmentAfterLogin, idp?.id); }; diff --git a/yarn.lock b/yarn.lock index 5e5e4838c3..aba9633a56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6629,9 +6629,9 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -"matrix-analytics-events@github:matrix-org/matrix-analytics-events.git#4aef17b56798639906f26a8739043a3c5c5fde7e": +"matrix-analytics-events@github:matrix-org/matrix-analytics-events.git#761751d7a96c129f118f1e3a89f27e77bbfddf9e": version "0.0.1" - resolved "https://codeload.github.com/matrix-org/matrix-analytics-events/tar.gz/4aef17b56798639906f26a8739043a3c5c5fde7e" + resolved "https://codeload.github.com/matrix-org/matrix-analytics-events/tar.gz/761751d7a96c129f118f1e3a89f27e77bbfddf9e" matrix-encrypt-attachment@^1.0.3: version "1.0.3"