-
-
Notifications
You must be signed in to change notification settings - Fork 812
Analytics opt in for posthog #6936
Changes from 50 commits
fdd8215
b136599
a90e49e
0178bcc
1942963
e9f0741
7def9c2
ff0726d
3547e91
c3b200b
d9ce993
6bd3c57
d9baaca
79c26bd
9e0f4a7
fa19b8a
a9fe002
b85a44e
76978a4
cd287fa
4216807
5612223
3cea363
4e0a973
5caf52b
3021333
f42d8b2
fbc38b3
17b86d6
5464742
38044a8
ec76c88
f620f89
d2ea642
7e1630f
c6a627b
2f5ab38
fd8d79c
b93c2a1
eba44d9
571f986
67fd6c5
ec6d829
26b537e
f35dfb4
40b1179
0070fd1
9667d0e
f155482
0a1cb02
c950db5
af11ff7
e75ccaa
becb105
dbeaadd
7c68aaa
3cb2c23
14f5523
3c40684
d9fedf6
a550b66
c256f72
72ad384
432c980
ec9c6ba
18a0654
269e786
98d156f
dfd360c
806545b
ed1c2b5
73db237
6628dbf
196d0d2
01c9574
0adedfc
560494f
dab34a7
1264c57
9eb010d
5bfafa7
1017e71
2a34286
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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,7 +132,7 @@ 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; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"; | ||
jryans marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
@@ -196,29 +196,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); | ||
|
|
@@ -306,7 +283,7 @@ export class PosthogAnalytics { | |
| if (this.enabled) { | ||
| this.posthog.reset(); | ||
| } | ||
| this.setAnonymity(Anonymity.Anonymous); | ||
| this.setAnonymity(Anonymity.Disabled); | ||
| } | ||
|
|
||
| public async trackPseudonymousEvent<E extends IPseudonymousEvent>( | ||
|
|
@@ -350,12 +327,31 @@ export class PosthogAnalytics { | |
| this.registerSuperProperties(this.platformSuperProperties); | ||
| } | ||
|
|
||
| public async updateAnonymityFromSettings(userId?: string): Promise<void> { | ||
| public async updateAnonymityFromSettings(pseudonumousOptIn: 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 = pseudonumousOptIn ? Anonymity.Pseudonymous : Anonymity.Disabled; | ||
| this.setAnonymity(anonymity); | ||
| if (anonymity == Anonymity.Pseudonymous) { | ||
novocaine marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| await this.identifyUser(MatrixClientPeg.get(), PosthogAnalytics.getRandomAnalyticsId); | ||
| } | ||
|
|
||
| if (anonymity != Anonymity.Disabled) { | ||
novocaine marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| await PosthogAnalytics.instance.updatePlatformSuperProperties(); | ||
| } | ||
| } | ||
|
|
||
| public startListeningToSettingsChanges(client: MatrixClient): 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); | ||
| }); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.