Integrate analytics stubs (#7186)
* Add matrix-analytics-events as a dependency * Make IEvent look like a stub definition * Update pageview tracking to track screens, using a hypothetical definition of a screen event * Remove distinction between pseudo and anon tracking, will need to rework it considering stubspull/21833/head
parent
684b0617ae
commit
43f264ccfc
|
@ -84,6 +84,7 @@
|
||||||
"katex": "^0.12.0",
|
"katex": "^0.12.0",
|
||||||
"linkifyjs": "^2.1.9",
|
"linkifyjs": "^2.1.9",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.20",
|
||||||
|
"matrix-analytics-events": "https://github.com/matrix-org/matrix-analytics-events.git#1eab4356548c97722a183912fda1ceabbe8cc7c1",
|
||||||
"maplibre-gl": "^1.15.2",
|
"maplibre-gl": "^1.15.2",
|
||||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||||
"matrix-widget-api": "^0.1.0-beta.18",
|
"matrix-widget-api": "^0.1.0-beta.18",
|
||||||
|
|
|
@ -25,8 +25,11 @@ export class DecryptionFailure {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TrackingFn = (count: number, trackedErrCode: string) => void;
|
type ErrorCode = "OlmKeysNotSentError" | "OlmIndexError" | "UnknownError" | "OlmUnspecifiedError";
|
||||||
type ErrCodeMapFn = (errcode: string) => string;
|
|
||||||
|
type TrackingFn = (count: number, trackedErrCode: ErrorCode) => void;
|
||||||
|
|
||||||
|
export type ErrCodeMapFn = (errcode: string) => ErrorCode;
|
||||||
|
|
||||||
export class DecryptionFailureTracker {
|
export class DecryptionFailureTracker {
|
||||||
// Array of items of type DecryptionFailure. Every `CHECK_INTERVAL_MS`, this list
|
// Array of items of type DecryptionFailure. Every `CHECK_INTERVAL_MS`, this list
|
||||||
|
@ -73,12 +76,12 @@ export class DecryptionFailureTracker {
|
||||||
* @param {function?} errorCodeMapFn The function used to map error codes to the
|
* @param {function?} errorCodeMapFn The function used to map error codes to the
|
||||||
* trackedErrorCode. If not provided, the `.code` of errors will be used.
|
* trackedErrorCode. If not provided, the `.code` of errors will be used.
|
||||||
*/
|
*/
|
||||||
constructor(private readonly fn: TrackingFn, private readonly errorCodeMapFn?: ErrCodeMapFn) {
|
constructor(private readonly fn: TrackingFn, private readonly errorCodeMapFn: ErrCodeMapFn) {
|
||||||
if (!fn || typeof fn !== 'function') {
|
if (!fn || typeof fn !== 'function') {
|
||||||
throw new Error('DecryptionFailureTracker requires tracking function');
|
throw new Error('DecryptionFailureTracker requires tracking function');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorCodeMapFn && typeof errorCodeMapFn !== 'function') {
|
if (typeof errorCodeMapFn !== 'function') {
|
||||||
throw new Error('DecryptionFailureTracker second constructor argument should be a function');
|
throw new Error('DecryptionFailureTracker second constructor argument should be a function');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,7 +198,7 @@ export class DecryptionFailureTracker {
|
||||||
public trackFailures(): void {
|
public trackFailures(): void {
|
||||||
for (const errorCode of Object.keys(this.failureCounts)) {
|
for (const errorCode of Object.keys(this.failureCounts)) {
|
||||||
if (this.failureCounts[errorCode] > 0) {
|
if (this.failureCounts[errorCode] > 0) {
|
||||||
const trackedErrorCode = this.errorCodeMapFn ? this.errorCodeMapFn(errorCode) : errorCode;
|
const trackedErrorCode = this.errorCodeMapFn(errorCode);
|
||||||
|
|
||||||
this.fn(this.failureCounts[errorCode], trackedErrorCode);
|
this.fn(this.failureCounts[errorCode], trackedErrorCode);
|
||||||
this.failureCounts[errorCode] = 0;
|
this.failureCounts[errorCode] = 0;
|
||||||
|
|
|
@ -40,12 +40,8 @@ import SettingsStore from "./settings/SettingsStore";
|
||||||
*/
|
*/
|
||||||
|
|
||||||
interface IEvent {
|
interface IEvent {
|
||||||
// The event name that will be used by PostHog. Event names should use snake_case.
|
// The event name that will be used by PostHog. Event names should use camelCase.
|
||||||
eventName: string;
|
eventName: string;
|
||||||
|
|
||||||
// The properties of the event that will be stored in PostHog. This is just a placeholder,
|
|
||||||
// extending interfaces must override this with a concrete definition to do type validation.
|
|
||||||
properties: {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Anonymity {
|
export enum Anonymity {
|
||||||
|
@ -54,39 +50,16 @@ export enum Anonymity {
|
||||||
Pseudonymous
|
Pseudonymous
|
||||||
}
|
}
|
||||||
|
|
||||||
// If an event extends IPseudonymousEvent, the event contains pseudonymous data
|
|
||||||
// that won't be sent unless the user has explicitly consented to pseudonymous tracking.
|
|
||||||
// For example, it might contain hashed user IDs or room IDs.
|
|
||||||
// Such events will be automatically dropped if PosthogAnalytics.anonymity isn't set to Pseudonymous.
|
|
||||||
export interface IPseudonymousEvent extends IEvent {}
|
|
||||||
|
|
||||||
// If an event extends IAnonymousEvent, the event strictly contains *only* anonymous data;
|
|
||||||
// i.e. no identifiers that can be associated with the user.
|
|
||||||
export interface IAnonymousEvent extends IEvent {}
|
|
||||||
|
|
||||||
export interface IRoomEvent extends IPseudonymousEvent {
|
|
||||||
hashedRoomId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IPageView extends IAnonymousEvent {
|
|
||||||
eventName: "$pageview";
|
|
||||||
properties: {
|
|
||||||
durationMs?: number;
|
|
||||||
screen?: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const whitelistedScreens = new Set([
|
const whitelistedScreens = new Set([
|
||||||
"register", "login", "forgot_password", "soft_logout", "new", "settings", "welcome", "home", "start", "directory",
|
"register", "login", "forgot_password", "soft_logout", "new", "settings", "welcome", "home", "start", "directory",
|
||||||
"start_sso", "start_cas", "groups", "complete_security", "post_registration", "room", "user", "group",
|
"start_sso", "start_cas", "groups", "complete_security", "post_registration", "room", "user", "group",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export async function getRedactedCurrentLocation(
|
export function getRedactedCurrentLocation(
|
||||||
origin: string,
|
origin: string,
|
||||||
hash: string,
|
hash: string,
|
||||||
pathname: string,
|
pathname: string,
|
||||||
anonymity: Anonymity,
|
): string {
|
||||||
): Promise<string> {
|
|
||||||
// Redact PII from the current location.
|
// Redact PII from the current location.
|
||||||
// For known screens, assumes a URL structure of /<screen name>/might/be/pii
|
// For known screens, assumes a URL structure of /<screen name>/might/be/pii
|
||||||
if (origin.startsWith('file://')) {
|
if (origin.startsWith('file://')) {
|
||||||
|
@ -219,13 +192,12 @@ export class PosthogAnalytics {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async capture(eventName: string, properties: posthog.Properties) {
|
private capture(eventName: string, properties: posthog.Properties) {
|
||||||
if (!this.enabled) {
|
if (!this.enabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { origin, hash, pathname } = window.location;
|
const { origin, hash, pathname } = window.location;
|
||||||
properties['$redacted_current_url'] = await getRedactedCurrentLocation(
|
properties['$redacted_current_url'] = getRedactedCurrentLocation(origin, hash, pathname);
|
||||||
origin, hash, pathname, this.anonymity);
|
|
||||||
this.posthog.capture(eventName, properties);
|
this.posthog.capture(eventName, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,35 +260,13 @@ export class PosthogAnalytics {
|
||||||
this.setAnonymity(Anonymity.Disabled);
|
this.setAnonymity(Anonymity.Disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async trackPseudonymousEvent<E extends IPseudonymousEvent>(
|
public trackEvent<E extends IEvent>(
|
||||||
eventName: E["eventName"],
|
event: E,
|
||||||
properties: E["properties"] = {},
|
): void {
|
||||||
) {
|
if (this.anonymity == Anonymity.Disabled || this.anonymity == Anonymity.Anonymous) return;
|
||||||
if (this.anonymity == Anonymity.Anonymous || this.anonymity == Anonymity.Disabled) return;
|
const eventWithoutName = { ...event };
|
||||||
await this.capture(eventName, properties);
|
delete eventWithoutName.eventName;
|
||||||
}
|
this.capture(event.eventName, eventWithoutName);
|
||||||
|
|
||||||
public async trackAnonymousEvent<E extends IAnonymousEvent>(
|
|
||||||
eventName: E["eventName"],
|
|
||||||
properties: E["properties"] = {},
|
|
||||||
): Promise<void> {
|
|
||||||
if (this.anonymity == Anonymity.Disabled) return;
|
|
||||||
await this.capture(eventName, properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async trackPageView(durationMs: number): Promise<void> {
|
|
||||||
const hash = window.location.hash;
|
|
||||||
|
|
||||||
let screen = null;
|
|
||||||
const split = hash.split("/");
|
|
||||||
if (split.length >= 2) {
|
|
||||||
screen = split[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.trackAnonymousEvent<IPageView>("$pageview", {
|
|
||||||
durationMs,
|
|
||||||
screen,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updatePlatformSuperProperties(): Promise<void> {
|
public async updatePlatformSuperProperties(): Promise<void> {
|
||||||
|
|
|
@ -18,6 +18,8 @@ import React, { ComponentType, createRef } from 'react';
|
||||||
import { createClient } from "matrix-js-sdk/src/matrix";
|
import { createClient } from "matrix-js-sdk/src/matrix";
|
||||||
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
|
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
|
import { Error as ErrorEvent } from "matrix-analytics-events/types/typescript/Error";
|
||||||
|
import { Screen as ScreenEvent } from "matrix-analytics-events/types/typescript/Screen";
|
||||||
import { defer, IDeferred, QueryDict } from "matrix-js-sdk/src/utils";
|
import { defer, IDeferred, QueryDict } from "matrix-js-sdk/src/utils";
|
||||||
|
|
||||||
// focus-visible is a Polyfill for the :focus-visible CSS pseudo-attribute used by _AccessibleButton.scss
|
// focus-visible is a Polyfill for the :focus-visible CSS pseudo-attribute used by _AccessibleButton.scss
|
||||||
|
@ -446,7 +448,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
const durationMs = this.stopPageChangeTimer();
|
const durationMs = this.stopPageChangeTimer();
|
||||||
Analytics.trackPageChange(durationMs);
|
Analytics.trackPageChange(durationMs);
|
||||||
CountlyAnalytics.instance.trackPageChange(durationMs);
|
CountlyAnalytics.instance.trackPageChange(durationMs);
|
||||||
PosthogAnalytics.instance.trackPageView(durationMs);
|
this.trackScreenChange(durationMs);
|
||||||
}
|
}
|
||||||
if (this.focusComposer) {
|
if (this.focusComposer) {
|
||||||
dis.fire(Action.FocusSendMessageComposer);
|
dis.fire(Action.FocusSendMessageComposer);
|
||||||
|
@ -465,6 +467,36 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer);
|
if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public trackScreenChange(durationMs: number): void {
|
||||||
|
const notLoggedInMap = {};
|
||||||
|
notLoggedInMap[Views.LOADING] = "WebLoading";
|
||||||
|
notLoggedInMap[Views.WELCOME] = "WebWelcome";
|
||||||
|
notLoggedInMap[Views.LOGIN] = "WebLogin";
|
||||||
|
notLoggedInMap[Views.REGISTER] = "WebRegister";
|
||||||
|
notLoggedInMap[Views.FORGOT_PASSWORD] = "WebForgotPassword";
|
||||||
|
notLoggedInMap[Views.COMPLETE_SECURITY] = "WebCompleteSecurity";
|
||||||
|
notLoggedInMap[Views.E2E_SETUP] = "WebE2ESetup";
|
||||||
|
notLoggedInMap[Views.SOFT_LOGOUT] = "WebSoftLogout";
|
||||||
|
|
||||||
|
const loggedInPageTypeMap = {};
|
||||||
|
loggedInPageTypeMap[PageType.HomePage] = "Home";
|
||||||
|
loggedInPageTypeMap[PageType.RoomView] = "Room";
|
||||||
|
loggedInPageTypeMap[PageType.RoomDirectory] = "RoomDirectory";
|
||||||
|
loggedInPageTypeMap[PageType.UserView] = "User";
|
||||||
|
loggedInPageTypeMap[PageType.GroupView] = "Group";
|
||||||
|
loggedInPageTypeMap[PageType.MyGroups] = "MyGroups";
|
||||||
|
|
||||||
|
const screenName = this.state.view === Views.LOGGED_IN ?
|
||||||
|
loggedInPageTypeMap[this.state.page_type] :
|
||||||
|
notLoggedInMap[this.state.view];
|
||||||
|
|
||||||
|
return PosthogAnalytics.instance.trackEvent<ScreenEvent>({
|
||||||
|
eventName: "Screen",
|
||||||
|
screenName,
|
||||||
|
durationMs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getFallbackHsUrl() {
|
getFallbackHsUrl() {
|
||||||
if (this.props.serverConfig && this.props.serverConfig.isDefault) {
|
if (this.props.serverConfig && this.props.serverConfig.isDefault) {
|
||||||
return this.props.config.fallback_hs_url;
|
return this.props.config.fallback_hs_url;
|
||||||
|
@ -1595,17 +1627,22 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
const dft = new DecryptionFailureTracker((total, errorCode) => {
|
const dft = new DecryptionFailureTracker((total, errorCode) => {
|
||||||
Analytics.trackEvent('E2E', 'Decryption failure', errorCode, String(total));
|
Analytics.trackEvent('E2E', 'Decryption failure', errorCode, String(total));
|
||||||
CountlyAnalytics.instance.track("decryption_failure", { errorCode }, null, { sum: total });
|
CountlyAnalytics.instance.track("decryption_failure", { errorCode }, null, { sum: total });
|
||||||
|
PosthogAnalytics.instance.trackEvent<ErrorEvent>({
|
||||||
|
eventName: "Error",
|
||||||
|
domain: "E2EE",
|
||||||
|
name: errorCode,
|
||||||
|
});
|
||||||
}, (errorCode) => {
|
}, (errorCode) => {
|
||||||
// Map JS-SDK error codes to tracker codes for aggregation
|
// Map JS-SDK error codes to tracker codes for aggregation
|
||||||
switch (errorCode) {
|
switch (errorCode) {
|
||||||
case 'MEGOLM_UNKNOWN_INBOUND_SESSION_ID':
|
case 'MEGOLM_UNKNOWN_INBOUND_SESSION_ID':
|
||||||
return 'olm_keys_not_sent_error';
|
return 'OlmKeysNotSentError';
|
||||||
case 'OLM_UNKNOWN_MESSAGE_INDEX':
|
case 'OLM_UNKNOWN_MESSAGE_INDEX':
|
||||||
return 'olm_index_error';
|
return 'OlmIndexError';
|
||||||
case undefined:
|
case undefined:
|
||||||
return 'unexpected_error';
|
return 'OlmUnspecifiedError';
|
||||||
default:
|
default:
|
||||||
return 'unspecified_error';
|
return 'UnknownError';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ describe('DecryptionFailureTracker', function() {
|
||||||
const failedDecryptionEvent = createFailedDecryptionEvent();
|
const failedDecryptionEvent = createFailedDecryptionEvent();
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
const tracker = new DecryptionFailureTracker((total) => count += total);
|
const tracker = new DecryptionFailureTracker((total) => count += total, () => "UnknownError");
|
||||||
|
|
||||||
const err = new MockDecryptionError();
|
const err = new MockDecryptionError();
|
||||||
tracker.eventDecrypted(failedDecryptionEvent, err);
|
tracker.eventDecrypted(failedDecryptionEvent, err);
|
||||||
|
@ -59,7 +59,7 @@ describe('DecryptionFailureTracker', function() {
|
||||||
const decryptedEvent = createFailedDecryptionEvent();
|
const decryptedEvent = createFailedDecryptionEvent();
|
||||||
const tracker = new DecryptionFailureTracker((total) => {
|
const tracker = new DecryptionFailureTracker((total) => {
|
||||||
expect(true).toBe(false, 'should not track an event that has since been decrypted correctly');
|
expect(true).toBe(false, 'should not track an event that has since been decrypted correctly');
|
||||||
});
|
}, () => "UnknownError");
|
||||||
|
|
||||||
const err = new MockDecryptionError();
|
const err = new MockDecryptionError();
|
||||||
tracker.eventDecrypted(decryptedEvent, err);
|
tracker.eventDecrypted(decryptedEvent, err);
|
||||||
|
@ -81,7 +81,7 @@ describe('DecryptionFailureTracker', function() {
|
||||||
const decryptedEvent2 = createFailedDecryptionEvent();
|
const decryptedEvent2 = createFailedDecryptionEvent();
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
const tracker = new DecryptionFailureTracker((total) => count += total);
|
const tracker = new DecryptionFailureTracker((total) => count += total, () => "UnknownError");
|
||||||
|
|
||||||
// Arbitrary number of failed decryptions for both events
|
// Arbitrary number of failed decryptions for both events
|
||||||
const err = new MockDecryptionError();
|
const err = new MockDecryptionError();
|
||||||
|
@ -112,7 +112,7 @@ describe('DecryptionFailureTracker', function() {
|
||||||
const decryptedEvent = createFailedDecryptionEvent();
|
const decryptedEvent = createFailedDecryptionEvent();
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
const tracker = new DecryptionFailureTracker((total) => count += total);
|
const tracker = new DecryptionFailureTracker((total) => count += total, () => "UnknownError");
|
||||||
|
|
||||||
// Indicate decryption
|
// Indicate decryption
|
||||||
const err = new MockDecryptionError();
|
const err = new MockDecryptionError();
|
||||||
|
@ -140,7 +140,7 @@ describe('DecryptionFailureTracker', function() {
|
||||||
const decryptedEvent = createFailedDecryptionEvent();
|
const decryptedEvent = createFailedDecryptionEvent();
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
const tracker = new DecryptionFailureTracker((total) => count += total);
|
const tracker = new DecryptionFailureTracker((total) => count += total, () => "UnknownError");
|
||||||
|
|
||||||
// Indicate decryption
|
// Indicate decryption
|
||||||
const err = new MockDecryptionError();
|
const err = new MockDecryptionError();
|
||||||
|
@ -153,7 +153,7 @@ describe('DecryptionFailureTracker', function() {
|
||||||
tracker.trackFailures();
|
tracker.trackFailures();
|
||||||
|
|
||||||
// Simulate the browser refreshing by destroying tracker and creating a new tracker
|
// Simulate the browser refreshing by destroying tracker and creating a new tracker
|
||||||
const secondTracker = new DecryptionFailureTracker((total) => count += total);
|
const secondTracker = new DecryptionFailureTracker((total) => count += total, () => "UnknownError");
|
||||||
|
|
||||||
//secondTracker.loadTrackedEventHashMap();
|
//secondTracker.loadTrackedEventHashMap();
|
||||||
|
|
||||||
|
@ -170,28 +170,29 @@ describe('DecryptionFailureTracker', function() {
|
||||||
const counts = {};
|
const counts = {};
|
||||||
const tracker = new DecryptionFailureTracker(
|
const tracker = new DecryptionFailureTracker(
|
||||||
(total, errorCode) => counts[errorCode] = (counts[errorCode] || 0) + total,
|
(total, errorCode) => counts[errorCode] = (counts[errorCode] || 0) + total,
|
||||||
|
(error) => error === "UnknownError" ? "UnknownError" : "OlmKeysNotSentError",
|
||||||
);
|
);
|
||||||
|
|
||||||
// One failure of ERROR_CODE_1, and effectively two for ERROR_CODE_2
|
// One failure of ERROR_CODE_1, and effectively two for ERROR_CODE_2
|
||||||
tracker.addDecryptionFailure(new DecryptionFailure('$event_id1', 'ERROR_CODE_1'));
|
tracker.addDecryptionFailure(new DecryptionFailure('$event_id1', 'UnknownError'));
|
||||||
tracker.addDecryptionFailure(new DecryptionFailure('$event_id2', 'ERROR_CODE_2'));
|
tracker.addDecryptionFailure(new DecryptionFailure('$event_id2', 'OlmKeysNotSentError'));
|
||||||
tracker.addDecryptionFailure(new DecryptionFailure('$event_id2', 'ERROR_CODE_2'));
|
tracker.addDecryptionFailure(new DecryptionFailure('$event_id2', 'OlmKeysNotSentError'));
|
||||||
tracker.addDecryptionFailure(new DecryptionFailure('$event_id3', 'ERROR_CODE_2'));
|
tracker.addDecryptionFailure(new DecryptionFailure('$event_id3', 'OlmKeysNotSentError'));
|
||||||
|
|
||||||
// Pretend "now" is Infinity
|
// Pretend "now" is Infinity
|
||||||
tracker.checkFailures(Infinity);
|
tracker.checkFailures(Infinity);
|
||||||
|
|
||||||
tracker.trackFailures();
|
tracker.trackFailures();
|
||||||
|
|
||||||
expect(counts['ERROR_CODE_1']).toBe(1, 'should track one ERROR_CODE_1');
|
expect(counts['UnknownError']).toBe(1, 'should track one UnknownError');
|
||||||
expect(counts['ERROR_CODE_2']).toBe(2, 'should track two ERROR_CODE_2');
|
expect(counts['OlmKeysNotSentError']).toBe(2, 'should track two OlmKeysNotSentError');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should map error codes correctly', () => {
|
it('should map error codes correctly', () => {
|
||||||
const counts = {};
|
const counts = {};
|
||||||
const tracker = new DecryptionFailureTracker(
|
const tracker = new DecryptionFailureTracker(
|
||||||
(total, errorCode) => counts[errorCode] = (counts[errorCode] || 0) + total,
|
(total, errorCode) => counts[errorCode] = (counts[errorCode] || 0) + total,
|
||||||
(errorCode) => 'MY_NEW_ERROR_CODE',
|
(errorCode) => 'OlmUnspecifiedError',
|
||||||
);
|
);
|
||||||
|
|
||||||
// One failure of ERROR_CODE_1, and effectively two for ERROR_CODE_2
|
// One failure of ERROR_CODE_1, and effectively two for ERROR_CODE_2
|
||||||
|
@ -204,7 +205,7 @@ describe('DecryptionFailureTracker', function() {
|
||||||
|
|
||||||
tracker.trackFailures();
|
tracker.trackFailures();
|
||||||
|
|
||||||
expect(counts['MY_NEW_ERROR_CODE'])
|
expect(counts['OlmUnspecifiedError'])
|
||||||
.toBe(3, 'should track three MY_NEW_ERROR_CODE, got ' + counts['MY_NEW_ERROR_CODE']);
|
.toBe(3, 'should track three OlmUnspecifiedError, got ' + counts['OlmUnspecifiedError']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,9 +17,7 @@ limitations under the License.
|
||||||
import {
|
import {
|
||||||
Anonymity,
|
Anonymity,
|
||||||
getRedactedCurrentLocation,
|
getRedactedCurrentLocation,
|
||||||
IAnonymousEvent,
|
IEvent,
|
||||||
IPseudonymousEvent,
|
|
||||||
IRoomEvent,
|
|
||||||
PosthogAnalytics,
|
PosthogAnalytics,
|
||||||
} from '../src/PosthogAnalytics';
|
} from '../src/PosthogAnalytics';
|
||||||
|
|
||||||
|
@ -41,25 +39,9 @@ class FakePosthog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITestEvent extends IAnonymousEvent {
|
export interface ITestEvent extends IEvent {
|
||||||
key: "jest_test_event";
|
eventName: "JestTestEvents";
|
||||||
properties: {
|
foo: string;
|
||||||
foo: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ITestPseudonymousEvent extends IPseudonymousEvent {
|
|
||||||
key: "jest_test_pseudo_event";
|
|
||||||
properties: {
|
|
||||||
foo: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ITestRoomEvent extends IRoomEvent {
|
|
||||||
key: "jest_test_room_event";
|
|
||||||
properties: {
|
|
||||||
foo: string;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("PosthogAnalytics", () => {
|
describe("PosthogAnalytics", () => {
|
||||||
|
@ -127,27 +109,20 @@ describe("PosthogAnalytics", () => {
|
||||||
analytics = new PosthogAnalytics(fakePosthog);
|
analytics = new PosthogAnalytics(fakePosthog);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should pass trackAnonymousEvent() to posthog", async () => {
|
it("Should pass event to posthog", () => {
|
||||||
analytics.setAnonymity(Anonymity.Pseudonymous);
|
analytics.setAnonymity(Anonymity.Pseudonymous);
|
||||||
await analytics.trackAnonymousEvent<ITestEvent>("jest_test_event", {
|
analytics.trackEvent<ITestEvent>({
|
||||||
|
eventName: "JestTestEvents",
|
||||||
foo: "bar",
|
foo: "bar",
|
||||||
});
|
});
|
||||||
expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_event");
|
expect(fakePosthog.capture.mock.calls[0][0]).toBe("JestTestEvents");
|
||||||
expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar");
|
expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should pass trackPseudonymousEvent() to posthog", async () => {
|
it("Should not track events if anonymous", async () => {
|
||||||
analytics.setAnonymity(Anonymity.Pseudonymous);
|
|
||||||
await analytics.trackPseudonymousEvent<ITestEvent>("jest_test_pseudo_event", {
|
|
||||||
foo: "bar",
|
|
||||||
});
|
|
||||||
expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_pseudo_event");
|
|
||||||
expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should not track pseudonymous messages if anonymous", async () => {
|
|
||||||
analytics.setAnonymity(Anonymity.Anonymous);
|
analytics.setAnonymity(Anonymity.Anonymous);
|
||||||
await analytics.trackPseudonymousEvent<ITestEvent>("jest_test_event", {
|
await analytics.trackEvent<ITestEvent>({
|
||||||
|
eventName: "JestTestEvents",
|
||||||
foo: "bar",
|
foo: "bar",
|
||||||
});
|
});
|
||||||
expect(fakePosthog.capture.mock.calls.length).toBe(0);
|
expect(fakePosthog.capture.mock.calls.length).toBe(0);
|
||||||
|
@ -155,43 +130,25 @@ describe("PosthogAnalytics", () => {
|
||||||
|
|
||||||
it("Should not track any events if disabled", async () => {
|
it("Should not track any events if disabled", async () => {
|
||||||
analytics.setAnonymity(Anonymity.Disabled);
|
analytics.setAnonymity(Anonymity.Disabled);
|
||||||
await analytics.trackPseudonymousEvent<ITestEvent>("jest_test_event", {
|
analytics.trackEvent<ITestEvent>({
|
||||||
|
eventName: "JestTestEvents",
|
||||||
foo: "bar",
|
foo: "bar",
|
||||||
});
|
});
|
||||||
await analytics.trackAnonymousEvent<ITestEvent>("jest_test_event", {
|
|
||||||
foo: "bar",
|
|
||||||
});
|
|
||||||
await analytics.trackPageView(200);
|
|
||||||
expect(fakePosthog.capture.mock.calls.length).toBe(0);
|
expect(fakePosthog.capture.mock.calls.length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should pseudonymise a location of a known screen", async () => {
|
it("Should anonymise location of a known screen", async () => {
|
||||||
const location = await getRedactedCurrentLocation(
|
const location = getRedactedCurrentLocation("https://foo.bar", "#/register/some/pii", "/");
|
||||||
"https://foo.bar", "#/register/some/pii", "/", Anonymity.Pseudonymous);
|
|
||||||
expect(location).toBe("https://foo.bar/#/register/<redacted>");
|
expect(location).toBe("https://foo.bar/#/register/<redacted>");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should anonymise a location of a known screen", async () => {
|
it("Should anonymise location of an unknown screen", async () => {
|
||||||
const location = await getRedactedCurrentLocation(
|
const location = getRedactedCurrentLocation("https://foo.bar", "#/not_a_screen_name/some/pii", "/");
|
||||||
"https://foo.bar", "#/register/some/pii", "/", Anonymity.Anonymous);
|
|
||||||
expect(location).toBe("https://foo.bar/#/register/<redacted>");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should pseudonymise a location of an unknown screen", async () => {
|
|
||||||
const location = await getRedactedCurrentLocation(
|
|
||||||
"https://foo.bar", "#/not_a_screen_name/some/pii", "/", Anonymity.Pseudonymous);
|
|
||||||
expect(location).toBe("https://foo.bar/#/<redacted_screen_name>/<redacted>");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should anonymise a location of an unknown screen", async () => {
|
|
||||||
const location = await getRedactedCurrentLocation(
|
|
||||||
"https://foo.bar", "#/not_a_screen_name/some/pii", "/", Anonymity.Anonymous);
|
|
||||||
expect(location).toBe("https://foo.bar/#/<redacted_screen_name>/<redacted>");
|
expect(location).toBe("https://foo.bar/#/<redacted_screen_name>/<redacted>");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should handle an empty hash", async () => {
|
it("Should handle an empty hash", async () => {
|
||||||
const location = await getRedactedCurrentLocation(
|
const location = getRedactedCurrentLocation("https://foo.bar", "", "/");
|
||||||
"https://foo.bar", "", "/", Anonymity.Anonymous);
|
|
||||||
expect(location).toBe("https://foo.bar/");
|
expect(location).toBe("https://foo.bar/");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -5988,6 +5988,10 @@ mathml-tag-names@^2.1.3:
|
||||||
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3"
|
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3"
|
||||||
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
|
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
|
||||||
|
|
||||||
|
"matrix-analytics-events@https://github.com/matrix-org/matrix-analytics-events.git#1eab4356548c97722a183912fda1ceabbe8cc7c1":
|
||||||
|
version "0.0.1"
|
||||||
|
resolved "https://github.com/matrix-org/matrix-analytics-events.git#1eab4356548c97722a183912fda1ceabbe8cc7c1"
|
||||||
|
|
||||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
|
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
|
||||||
version "15.1.1"
|
version "15.1.1"
|
||||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/55b489b17a519bc0d557c47ac9fe7ce54690b602"
|
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/55b489b17a519bc0d557c47ac9fe7ce54690b602"
|
||||||
|
|
Loading…
Reference in New Issue