Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Analytics opt in for posthog #6936

Merged
merged 83 commits into from
Dec 5, 2021
Merged
Show file tree
Hide file tree
Changes from 73 commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
fdd8215
Make pseudonymousAnalyticsOptIn the new analytics opt in flag
novocaine Sep 15, 2021
b136599
Note that we send an Analytics ID
novocaine Sep 15, 2021
a90e49e
Remove "anonymous" term.
novocaine Sep 15, 2021
0178bcc
Toast shows explanation about re-opt-in if necessary
novocaine Sep 16, 2021
1942963
Merge remote-tracking branch 'origin' into analytics-pseudo-opt-in
novocaine Sep 16, 2021
e9f0741
stash wip
novocaine Sep 29, 2021
7def9c2
Merge remote-tracking branch 'origin' into analytics-pseudo-opt-in
novocaine Oct 13, 2021
ff0726d
fix lint
novocaine Oct 13, 2021
3547e91
Fix settings controller/level
novocaine Oct 13, 2021
c3b200b
Listen only to events from account data sync
novocaine Oct 13, 2021
d9ce993
fix toast on startup
novocaine Oct 13, 2021
6bd3c57
Toast display relies on sync events
novocaine Oct 14, 2021
d9baaca
Merge remote-tracking branch 'origin' into analytics-pseudo-opt-in
novocaine Oct 14, 2021
79c26bd
Fix toast behaviour
novocaine Oct 14, 2021
9e0f4a7
Pass flag into showToast so its up to date
novocaine Oct 14, 2021
fa19b8a
Explicitly check if showPrompt is true as well as waiting for events
novocaine Oct 14, 2021
a9fe002
slightly clarify comment
novocaine Oct 14, 2021
b85a44e
Revert to enabling old analytics off old flag
novocaine Oct 14, 2021
76978a4
Add comment
novocaine Oct 14, 2021
cd287fa
Fix lint
novocaine Oct 14, 2021
4216807
Preserve existing analytics libraries
novocaine Oct 14, 2021
5612223
Add ability to customise the analytics owner
novocaine Oct 14, 2021
3cea363
Don't stop listening - flag can flip true/false during init
novocaine Oct 14, 2021
4e0a973
fix type lint
novocaine Oct 14, 2021
5caf52b
fix imports
novocaine Oct 14, 2021
3021333
Make analyticsOwner orthogonal from posthog
novocaine Oct 14, 2021
f42d8b2
Update src/Analytics.tsx
Oct 14, 2021
fbc38b3
Explicitly state private readonly
novocaine Oct 15, 2021
17b86d6
Use SettingsStore.watchSetting rather than on acountData
novocaine Oct 15, 2021
5464742
camelcase
novocaine Oct 15, 2021
38044a8
Add noreferrer noopener
novocaine Oct 15, 2021
ec76c88
Use watchSetting to handle race condition
novocaine Oct 15, 2021
f620f89
Preserve old toast unless posthog is enabled
novocaine Oct 15, 2021
d2ea642
Prefences doesn't switch to using posthog config unless posthog is sw…
novocaine Oct 18, 2021
7e1630f
Link to cookie policy from analytis page
novocaine Oct 18, 2021
c6a627b
Merge remote-tracking branch 'origin' into analytics-pseudo-opt-in
novocaine Oct 18, 2021
2f5ab38
Reformat translated strings using concat
novocaine Oct 18, 2021
fd8d79c
Translate cookie policy link
novocaine Oct 18, 2021
b93c2a1
Reinstate listed data sent as part of analytics
novocaine Oct 18, 2021
eba44d9
denote XXX
novocaine Oct 18, 2021
571f986
convert accountData watcher to use SettingsStore
novocaine Oct 18, 2021
67fd6c5
fix camelCase
novocaine Oct 18, 2021
ec6d829
Update wording according to review
novocaine Oct 21, 2021
26b537e
Update wording in preferences to be consistent with toast
novocaine Oct 21, 2021
f35dfb4
Explicitly link to more information
novocaine Oct 21, 2021
40b1179
Make labels in analytics table wider so the modal isn't so high
novocaine Oct 21, 2021
0070fd1
Disable analytics on logout
novocaine Oct 21, 2021
9667d0e
don't wrap line unnecessarily
novocaine Oct 22, 2021
f155482
remove *typographic* quotation marks
novocaine Oct 22, 2021
0a1cb02
i18n
novocaine Oct 22, 2021
c950db5
Move analytics settings to im.vector.analytics
novocaine Oct 26, 2021
af11ff7
remove showPseudonymousAnalyticsPrompt
novocaine Oct 29, 2021
e75ccaa
Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into…
novocaine Oct 29, 2021
becb105
Cleanup
novocaine Oct 29, 2021
dbeaadd
Update copy
novocaine Oct 29, 2021
7c68aaa
Create new analytics Learn More dialog, hook it up to the toast
novocaine Nov 17, 2021
3cb2c23
Fix all copy, style the dialog
novocaine Nov 18, 2021
14f5523
Merge remote-tracking branch 'origin' into analytics-pseudo-opt-in
novocaine Nov 18, 2021
3c40684
fix whitespace
novocaine Nov 18, 2021
d9fedf6
$accent-color -> $accent
novocaine Nov 18, 2021
a550b66
Fix another $accent, and don't mark privacyPolicyUrl as optional
novocaine Nov 18, 2021
c256f72
_tBold breaks i18n static parsing
novocaine Nov 18, 2021
72ad384
Update e2e tests
novocaine Nov 18, 2021
432c980
Show new learn more from analytics
novocaine Nov 19, 2021
ec9c6ba
Remove analytics ID from old analytics details, which is shown for no…
novocaine Nov 19, 2021
18a0654
Remove period from link title
novocaine Nov 19, 2021
269e786
Fix analytics toast not working for non-posthog
novocaine Nov 19, 2021
98d156f
Fix privacyPolicyUrl variable name
novocaine Nov 25, 2021
dfd360c
Tweak padding and margin per design's request
novocaine Nov 25, 2021
806545b
Add copyright
novocaine Nov 25, 2021
ed1c2b5
Change indent
novocaine Nov 25, 2021
73db237
use Action enums
novocaine Nov 25, 2021
6628dbf
Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into…
novocaine Nov 25, 2021
196d0d2
Update styling for toast buttons to be identical
novocaine Nov 29, 2021
01c9574
Update copy
novocaine Nov 29, 2021
0adedfc
i18n
novocaine Nov 29, 2021
560494f
Put cookie link back in old toast
novocaine Nov 29, 2021
dab34a7
Update src/PosthogAnalytics.ts
Nov 30, 2021
1264c57
Update src/PosthogAnalytics.ts
Nov 30, 2021
9eb010d
Add comments indicating no payload
novocaine Nov 30, 2021
5bfafa7
Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into…
novocaine Dec 2, 2021
1017e71
fix botched merge commit
novocaine Dec 2, 2021
2a34286
use $font-weight-semi-bold
novocaine Dec 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions res/css/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
@import "./views/dialogs/_AddExistingToSpaceDialog.scss";
@import "./views/dialogs/_AddressPickerDialog.scss";
@import "./views/dialogs/_Analytics.scss";
@import "./views/dialogs/_AnalyticsLearnMoreDialog.scss";
@import "./views/dialogs/_BugReportDialog.scss";
@import "./views/dialogs/_ChangelogDialog.scss";
@import "./views/dialogs/_ChatCreateOrReuseChatDialog.scss";
Expand Down
4 changes: 4 additions & 0 deletions res/css/views/dialogs/_Analytics.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ limitations under the License.

.mx_AnalyticsModal table {
margin: 10px 0px;

.mx_AnalyticsModal_label {
width: 400px;
}
}
64 changes: 64 additions & 0 deletions res/css/views/dialogs/_AnalyticsLearnMoreDialog.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

.mx_AnalyticsLearnMoreDialog {
t3chguy marked this conversation as resolved.
Show resolved Hide resolved
max-width: 500px;
.mx_AnalyticsLearnMore_image_holder {
background-image: url('$(res)/img/element-shiny.svg');
background-repeat: no-repeat;
background-position: center top;
height: 112px;
padding: 20px 0px;
}

.mx_Dialog_content {
margin-bottom: 0px;
}

.mx_AnalyticsLearnMore_copy {
border-bottom: 1px solid $menu-border-color;
padding-bottom: 20px;
margin-bottom: 20px;
}

a {
color: $accent;
text-decoration: none;
}

.mx_AnalyticsPolicyLink {
display: inline-block;
mask-image: url('$(res)/img/external-link.svg');
background-color: $accent;
mask-repeat: no-repeat;
mask-size: contain;
width: 12px;
height: 12px;
margin-left: 3px;
vertical-align: middle;
}

.mx_AnalyticsLearnMore_bullets {
padding-left: 0px;
}

.mx_AnalyticsLearnMore_bullets li {
background: url('$(res)/img/tick-circle.svg') no-repeat;
list-style-type: none;
padding: 2px 0px 20px 32px;
vertical-align: middle;
}
}
15 changes: 15 additions & 0 deletions res/img/element-shiny.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions res/img/tick-circle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 12 additions & 2 deletions src/Analytics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -393,16 +393,26 @@ export class Analytics {
];

// FIXME: Using an import will result in test failures
const cookiePolicyUrl = SdkConfig.get().piwik?.policyUrl;
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
const cookiePolicyLink = _t(
"Our complete cookie policy can be found <CookiePolicyLink>here</CookiePolicyLink>.",
{},
{
"CookiePolicyLink": (sub) => {
return <a href={cookiePolicyUrl} target="_blank" rel="noreferrer noopener">{ sub }</a>;
},
});
Modal.createTrackedDialog('Analytics Details', '', ErrorDialog, {
title: _t('Analytics'),
description: <div className="mx_AnalyticsModal">
<div>{ _t('The information being sent to us to help make %(brand)s better includes:', {
{ cookiePolicyUrl && <p>{ cookiePolicyLink }</p> }
<div>{ _t('Some examples of the information being sent to us to help make %(brand)s better includes:', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Some examples" feels like we're hiding something when we're not - the list should be exhaustive. Not a blocker for merge, but this did catch me off guard.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intend to collate a full data dictionary which will denote example what each event and property does - primarily so people trying to do analysis understand what events correspond to, but a secondary benefit would be that it'll be very transparent what we're tracking.

Note that the previous wording was "The information being sent to us to help make %(brand)s better includes", and what is there now is quite partial (we track 29 events through Matomo, and they and their properties not documented here - this I think is just properties sent with every event).

So I actually think drawing a bit more attention to the weaselness is clarifying.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@novocaine once you've completed that list are you able to share with me so that it can be replicated on the cookie policy?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are recording the data dictionary over here in JSON schema. Clients cant send events without adding to this schema.

So, we can generate something human readable from this. I'll update you once that's complee.

brand: SdkConfig.get().brand,
}) }</div>
<table>
{ rows.map((row) => <tr key={row[0]}>
<td>{ _t(
<td className="mx_AnalyticsModal_label">{ _t(
customVariables[row[0]].expl,
customVariables[row[0]].getTextVariables ?
customVariables[row[0]].getTextVariables() :
Expand Down
7 changes: 4 additions & 3 deletions src/Lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -585,12 +585,13 @@ async function doSetLoggedIn(

MatrixClientPeg.replaceUsingCreds(credentials);

PosthogAnalytics.instance.updateAnonymityFromSettings(credentials.userId);

setSentryUser(credentials.userId);

const client = MatrixClientPeg.get();
if (PosthogAnalytics.instance.isEnabled()) {
PosthogAnalytics.instance.startListeningToSettingsChanges();
}

const client = MatrixClientPeg.get();
if (credentials.freshLogin && SettingsStore.getValue("feature_dehydration")) {
// If we just logged in, try to rehydrate a device instead of using a
// new device. If it succeeds, we'll get a new device ID, so make sure
Expand Down
63 changes: 30 additions & 33 deletions src/PosthogAnalytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

readonly feels awkward for this sort of field (does it really never change from false?)

Copy link
Contributor Author

@novocaine novocaine Oct 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this reinforces the semantics nicely - it only ever needs to gets decided once in the constructor (allowed by readonly semantics) because its set based on something that can't change at runtime (sdk config)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we get a comment to say that it's decided in the constructor? Our typical pattern isn't to create new instances as needed, but rather toggle an internal flag.

Copy link
Contributor Author

@novocaine novocaine Nov 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it does have an existing comment, happy to reword it if you have a suggestion that'd make it clearer

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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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.
Expand All @@ -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) {
Expand All @@ -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>(
Expand Down Expand Up @@ -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) {
novocaine marked this conversation as resolved.
Show resolved Hide resolved
await this.identifyUser(MatrixClientPeg.get(), PosthogAnalytics.getRandomAnalyticsId);
}

if (anonymity != Anonymity.Disabled) {
novocaine marked this conversation as resolved.
Show resolved Hide resolved
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);
});
}
}
Loading