Analytics opt in for posthog (#6936)

* Add a new flag pseudonymousAnalyticsOptIn replacing analyticsOptIn, stored at account level, so people only need to opt in once.

* Show a toast in login to users that have analyticsOptIn set but not yet pseudonymousAnalyticsOptIn prompting them confirm the new method is okay. Update the copy of the existing opt-in toast. Don't notify users that previously opted out.

* Update the copy in settings

* Add a new learn more dialog

* Support a new config flag analyticsOwner which is used in these toasts when explaining which entity the data is sent to ("Help improve %(analyticsOwner)"). If unset, display brand. This allows deployments whose brand differs from the receiver of the analytics to explain the situation to their users (e.g. AcmeCorp badges their app, but explains the data is sent to Element, not them)

* The new opt-in and flags are only used when posthog is configured; prior to that there are no changes to UX or tracking behaviour.
This commit is contained in:
James Salter
2021-12-06 09:39:33 +11:00
committed by GitHub
parent 961fec9081
commit 5219b6be80
19 changed files with 512 additions and 150 deletions
+30 -33
View File
@@ -17,11 +17,11 @@ limitations under the License.
import posthog, { PostHog } from 'posthog-js';
import PlatformPeg from './PlatformPeg';
import SdkConfig from './SdkConfig';
import SettingsStore from './settings/SettingsStore';
import { MatrixClientPeg } from "./MatrixClientPeg";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { logger } from "matrix-js-sdk/src/logger";
import SettingsStore from "./settings/SettingsStore";
/* Posthog analytics tracking.
*
@@ -132,10 +132,10 @@ export class PosthogAnalytics {
private anonymity = Anonymity.Disabled;
// set true during the constructor if posthog config is present, otherwise false
private enabled = false;
private readonly enabled: boolean = false;
private static _instance = null;
private platformSuperProperties = {};
private static ANALYTICS_ID_EVENT_TYPE = "im.vector.web.analytics_id";
private static ANALYTICS_EVENT_TYPE = "im.vector.analytics";
public static get instance(): PosthogAnalytics {
if (!this._instance) {
@@ -197,29 +197,6 @@ export class PosthogAnalytics {
return properties;
};
private static getAnonymityFromSettings(): Anonymity {
// determine the current anonymity level based on current user settings
// "Send anonymous usage data which helps us improve Element. This will use a cookie."
const analyticsOptIn = SettingsStore.getValue("analyticsOptIn", null, true);
// (proposed wording) "Send pseudonymous usage data which helps us improve Element. This will use a cookie."
//
// TODO: Currently, this is only a labs flag, for testing purposes.
const pseudonumousOptIn = SettingsStore.getValue("feature_pseudonymous_analytics_opt_in", null, true);
let anonymity;
if (pseudonumousOptIn) {
anonymity = Anonymity.Pseudonymous;
} else if (analyticsOptIn) {
anonymity = Anonymity.Anonymous;
} else {
anonymity = Anonymity.Disabled;
}
return anonymity;
}
private registerSuperProperties(properties: posthog.Properties) {
if (this.enabled) {
this.posthog.register(properties);
@@ -279,7 +256,7 @@ export class PosthogAnalytics {
// Check the user's account_data for an analytics ID to use. Storing the ID in account_data allows
// different devices to send the same ID.
try {
const accountData = await client.getAccountDataFromServer(PosthogAnalytics.ANALYTICS_ID_EVENT_TYPE);
const accountData = await client.getAccountDataFromServer(PosthogAnalytics.ANALYTICS_EVENT_TYPE);
let analyticsID = accountData?.id;
if (!analyticsID) {
// Couldn't retrieve an analytics ID from user settings, so create one and set it on the server.
@@ -288,7 +265,8 @@ export class PosthogAnalytics {
// until the next time account data is refreshed and this function is called (most likely on next
// page load). This will happen pretty infrequently, so we can tolerate the possibility.
analyticsID = analyticsIdGenerator();
await client.setAccountData("im.vector.web.analytics_id", { id: analyticsID });
await client.setAccountData(PosthogAnalytics.ANALYTICS_EVENT_TYPE,
Object.assign({ id: analyticsID }, accountData));
}
this.posthog.identify(analyticsID);
} catch (e) {
@@ -307,7 +285,7 @@ export class PosthogAnalytics {
if (this.enabled) {
this.posthog.reset();
}
this.setAnonymity(Anonymity.Anonymous);
this.setAnonymity(Anonymity.Disabled);
}
public async trackPseudonymousEvent<E extends IPseudonymousEvent>(
@@ -351,12 +329,31 @@ export class PosthogAnalytics {
this.registerSuperProperties(this.platformSuperProperties);
}
public async updateAnonymityFromSettings(userId?: string): Promise<void> {
public async updateAnonymityFromSettings(pseudonymousOptIn: boolean): Promise<void> {
// Update this.anonymity based on the user's analytics opt-in settings
// Identify the user (via hashed user ID) to posthog if anonymity is pseudonmyous
this.setAnonymity(PosthogAnalytics.getAnonymityFromSettings());
if (userId && this.getAnonymity() == Anonymity.Pseudonymous) {
const anonymity = pseudonymousOptIn ? Anonymity.Pseudonymous : Anonymity.Disabled;
this.setAnonymity(anonymity);
if (anonymity === Anonymity.Pseudonymous) {
await this.identifyUser(MatrixClientPeg.get(), PosthogAnalytics.getRandomAnalyticsId);
}
if (anonymity !== Anonymity.Disabled) {
await PosthogAnalytics.instance.updatePlatformSuperProperties();
}
}
public startListeningToSettingsChanges(): void {
// Listen to account data changes from sync so we can observe changes to relevant flags and update.
// This is called -
// * On page load, when the account data is first received by sync
// * On login
// * When another device changes account data
// * When the user changes their preferences on this device
// Note that for new accounts, pseudonymousAnalyticsOptIn won't be set, so updateAnonymityFromSettings
// won't be called (i.e. this.anonymity will be left as the default, until the setting changes)
SettingsStore.watchSetting("pseudonymousAnalyticsOptIn", null,
(originalSettingName, changedInRoomId, atLevel, newValueAtLevel, newValue) => {
this.updateAnonymityFromSettings(!!newValue);
});
}
}