mirror of
https://github.com/vector-im/element-call.git
synced 2026-05-25 11:14:37 +00:00
Posthog add reconnect event (#3953)
* Add Posthog events for Call reconnect including the reason * Expose single trackCallReconnecting() entry point on PosthogAnalytics * Track reconnect duration and align with existing analytics pattern * Refactor combined$ to return [connected, reason] tuple * Update firefoxUserPrefs to allow getUserMedia and enumerateDevices on CI --------- Co-authored-by: Valere <bill.carson@valrsoft.com> Co-authored-by: Robin <robin@robin.town> Co-authored-by: Timo K <toger5@hotmail.de>
This commit is contained in:
@@ -26,6 +26,7 @@ import {
|
||||
QualitySurveyEventTracker,
|
||||
CallDisconnectedEventTracker,
|
||||
CallConnectDurationTracker,
|
||||
CallReconnectingTracker,
|
||||
} from "./PosthogEvents";
|
||||
import { Config } from "../config/Config";
|
||||
import { getUrlParams } from "../UrlParams";
|
||||
@@ -421,4 +422,5 @@ export class PosthogAnalytics {
|
||||
public eventQualitySurvey = new QualitySurveyEventTracker();
|
||||
public eventCallDisconnected = new CallDisconnectedEventTracker();
|
||||
public eventCallConnectDuration = new CallConnectDurationTracker();
|
||||
public eventCallReconnecting = new CallReconnectingTracker();
|
||||
}
|
||||
|
||||
@@ -18,7 +18,11 @@ import { logger } from "matrix-js-sdk/lib/logger";
|
||||
import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc";
|
||||
|
||||
import { PosthogAnalytics } from "./PosthogAnalytics";
|
||||
import { CallEndedTracker } from "./PosthogEvents";
|
||||
import {
|
||||
CallEndedTracker,
|
||||
CallReconnectingTracker,
|
||||
type CallReconnectingReason,
|
||||
} from "./PosthogEvents";
|
||||
import { mockConfig } from "../utils/test";
|
||||
|
||||
const defaultCounters = {
|
||||
@@ -89,6 +93,11 @@ describe("CallEnded", () => {
|
||||
roomEventEncryptionKeysSent: 10,
|
||||
roomEventEncryptionKeysReceived: 5,
|
||||
roomEventEncryptionKeysReceivedAverageAge: 100,
|
||||
callReconnectingCount: 0,
|
||||
callReconnectingCountSync: 0,
|
||||
callReconnectingCountMembership: 0,
|
||||
callReconnectingCountProbablyLeft: 0,
|
||||
callReconnectingCountLivekit: 0,
|
||||
},
|
||||
{ send_instantly: true },
|
||||
);
|
||||
@@ -159,4 +168,70 @@ describe("CallEnded", () => {
|
||||
{ send_instantly: false },
|
||||
);
|
||||
});
|
||||
|
||||
it("includes per-reason reconnecting counts in CallEnded", () => {
|
||||
const tracker = new CallEndedTracker();
|
||||
const mockSession = createMockRtcSession();
|
||||
|
||||
tracker.cacheStartCall(new Date());
|
||||
tracker.cacheReconnecting("sync");
|
||||
tracker.cacheReconnecting("sync");
|
||||
tracker.cacheReconnecting("livekit");
|
||||
tracker.cacheReconnecting("membership");
|
||||
tracker.track("test-call-id", 1, false, mockSession);
|
||||
|
||||
expect(PosthogAnalytics.instance.trackEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
callReconnectingCount: 4,
|
||||
callReconnectingCountSync: 2,
|
||||
callReconnectingCountMembership: 1,
|
||||
callReconnectingCountProbablyLeft: 0,
|
||||
callReconnectingCountLivekit: 1,
|
||||
}),
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("CallReconnecting", () => {
|
||||
beforeAll(() => {
|
||||
mockConfig();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.spyOn(PosthogAnalytics.instance, "trackEvent").mockImplementation(
|
||||
() => {},
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
PosthogAnalytics.resetInstance();
|
||||
});
|
||||
|
||||
it("tracks event with correct shape", () => {
|
||||
const tracker = new CallReconnectingTracker();
|
||||
tracker.track("!room:example.org", "sync", 3.5);
|
||||
|
||||
expect(PosthogAnalytics.instance.trackEvent).toHaveBeenCalledWith({
|
||||
eventName: "CallReconnecting",
|
||||
callId: "!room:example.org",
|
||||
reason: "sync",
|
||||
reconnectDuration: 3.5,
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
"sync",
|
||||
"membership",
|
||||
"probablyLeft",
|
||||
"livekit",
|
||||
] as CallReconnectingReason[])("tracks reason %s correctly", (reason) => {
|
||||
const tracker = new CallReconnectingTracker();
|
||||
tracker.track("!room:example.org", reason, 1.0);
|
||||
|
||||
expect(PosthogAnalytics.instance.trackEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ reason, reconnectDuration: 1.0 }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
|
||||
interface CallEnded extends IPosthogEvent {
|
||||
eventName: "CallEnded";
|
||||
// the callId posthog key is essentially a Matrix roomId
|
||||
callId: string;
|
||||
callParticipantsOnLeave: number;
|
||||
callParticipantsMax: number;
|
||||
@@ -24,16 +25,43 @@ interface CallEnded extends IPosthogEvent {
|
||||
roomEventEncryptionKeysSent: number;
|
||||
roomEventEncryptionKeysReceived: number;
|
||||
roomEventEncryptionKeysReceivedAverageAge: number;
|
||||
callReconnectingCount: number;
|
||||
callReconnectingCountSync: number;
|
||||
callReconnectingCountMembership: number;
|
||||
callReconnectingCountProbablyLeft: number;
|
||||
callReconnectingCountLivekit: number;
|
||||
}
|
||||
|
||||
export class CallEndedTracker {
|
||||
private cache: { startTime?: Date; maxParticipantsCount: number } = {
|
||||
private cache: {
|
||||
startTime?: Date;
|
||||
maxParticipantsCount: number;
|
||||
reconnectingCount: number;
|
||||
reconnectingCountByReason: Record<CallReconnectingReason, number>;
|
||||
} = {
|
||||
startTime: undefined,
|
||||
maxParticipantsCount: 0,
|
||||
reconnectingCount: 0,
|
||||
reconnectingCountByReason: {
|
||||
sync: 0,
|
||||
membership: 0,
|
||||
probablyLeft: 0,
|
||||
livekit: 0,
|
||||
},
|
||||
};
|
||||
|
||||
public cacheStartCall(time: Date): void {
|
||||
this.cache.startTime = time;
|
||||
this.cache = {
|
||||
startTime: time,
|
||||
maxParticipantsCount: 0,
|
||||
reconnectingCount: 0,
|
||||
reconnectingCountByReason: {
|
||||
sync: 0,
|
||||
membership: 0,
|
||||
probablyLeft: 0,
|
||||
livekit: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public cacheParticipantCountChanged(count: number): void {
|
||||
@@ -43,6 +71,11 @@ export class CallEndedTracker {
|
||||
);
|
||||
}
|
||||
|
||||
public cacheReconnecting(reason: CallReconnectingReason): void {
|
||||
this.cache.reconnectingCount++;
|
||||
this.cache.reconnectingCountByReason[reason]++;
|
||||
}
|
||||
|
||||
public track(
|
||||
callId: string,
|
||||
callParticipantsNow: number,
|
||||
@@ -67,6 +100,14 @@ export class CallEndedTracker {
|
||||
.roomEventEncryptionKeysReceivedTotalAge /
|
||||
rtcSession.statistics.counters.roomEventEncryptionKeysReceived
|
||||
: 0,
|
||||
callReconnectingCount: this.cache.reconnectingCount,
|
||||
callReconnectingCountSync: this.cache.reconnectingCountByReason.sync,
|
||||
callReconnectingCountMembership:
|
||||
this.cache.reconnectingCountByReason.membership,
|
||||
callReconnectingCountProbablyLeft:
|
||||
this.cache.reconnectingCountByReason.probablyLeft,
|
||||
callReconnectingCountLivekit:
|
||||
this.cache.reconnectingCountByReason.livekit,
|
||||
},
|
||||
{ send_instantly: sendInstantly },
|
||||
);
|
||||
@@ -80,6 +121,7 @@ export class CallEndedTracker {
|
||||
|
||||
interface CallStarted extends IPosthogEvent {
|
||||
eventName: "CallStarted";
|
||||
// the callId posthog key is essentially a Matrix roomId
|
||||
callId: string;
|
||||
}
|
||||
|
||||
@@ -140,6 +182,7 @@ export class LoginTracker {
|
||||
interface MuteMicrophone {
|
||||
eventName: "MuteMicrophone";
|
||||
targetMuteState: "mute" | "unmute";
|
||||
// the callId posthog key is essentially a Matrix roomId
|
||||
callId: string;
|
||||
}
|
||||
|
||||
@@ -156,6 +199,7 @@ export class MuteMicrophoneTracker {
|
||||
interface MuteCamera {
|
||||
eventName: "MuteCamera";
|
||||
targetMuteState: "mute" | "unmute";
|
||||
// the callId posthog key is essentially a Matrix roomId
|
||||
callId: string;
|
||||
}
|
||||
|
||||
@@ -171,6 +215,7 @@ export class MuteCameraTracker {
|
||||
|
||||
interface UndecryptableToDeviceEvent {
|
||||
eventName: "UndecryptableToDeviceEvent";
|
||||
// the callId posthog key is essentially a Matrix roomId
|
||||
callId: string;
|
||||
}
|
||||
|
||||
@@ -185,6 +230,7 @@ export class UndecryptableToDeviceEventTracker {
|
||||
|
||||
interface QualitySurveyEvent {
|
||||
eventName: "QualitySurvey";
|
||||
// the callId posthog key is essentially a Matrix roomId
|
||||
callId: string;
|
||||
feedbackText: string;
|
||||
stars: number;
|
||||
@@ -249,3 +295,32 @@ export class CallConnectDurationTracker {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export type CallReconnectingReason =
|
||||
| "sync"
|
||||
| "membership"
|
||||
| "probablyLeft"
|
||||
| "livekit";
|
||||
|
||||
interface CallReconnecting extends IPosthogEvent {
|
||||
eventName: "CallReconnecting";
|
||||
// the callId posthog key is essentially a Matrix roomId
|
||||
callId: string;
|
||||
reason: CallReconnectingReason;
|
||||
reconnectDuration: number;
|
||||
}
|
||||
|
||||
export class CallReconnectingTracker {
|
||||
public track(
|
||||
callId: string,
|
||||
reason: CallReconnectingReason,
|
||||
reconnectDuration: number,
|
||||
): void {
|
||||
PosthogAnalytics.instance.trackEvent<CallReconnecting>({
|
||||
eventName: "CallReconnecting",
|
||||
callId,
|
||||
reason,
|
||||
reconnectDuration,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user