Merge pull request #983 from robintown/rejoin-analytics
Track call rejoins
This commit is contained in:
commit
61a0534984
2 changed files with 75 additions and 47 deletions
|
|
@ -16,12 +16,29 @@ limitations under the License.
|
||||||
|
|
||||||
import { SpanExporter, ReadableSpan } from "@opentelemetry/sdk-trace-base";
|
import { SpanExporter, ReadableSpan } from "@opentelemetry/sdk-trace-base";
|
||||||
import { ExportResult, ExportResultCode } from "@opentelemetry/core";
|
import { ExportResult, ExportResultCode } from "@opentelemetry/core";
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { HrTime } from "@opentelemetry/api";
|
||||||
|
|
||||||
import { PosthogAnalytics } from "./PosthogAnalytics";
|
import { PosthogAnalytics } from "./PosthogAnalytics";
|
||||||
|
|
||||||
|
interface PrevCall {
|
||||||
|
callId: string;
|
||||||
|
hangupTs: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hrTimeToMs(time: HrTime): number {
|
||||||
|
return time[0] * 1000 + time[1] * 0.000001;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is implementation of {@link SpanExporter} that sends spans
|
* The maximum time between hanging up and joining the same call that we would
|
||||||
* to Posthog
|
* consider a 'rejoin' on the user's part.
|
||||||
|
*/
|
||||||
|
const maxRejoinMs = 2 * 60 * 1000; // 2 minutes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is implementation of {@link SpanExporter} that extracts certain metrics
|
||||||
|
* from spans to send to PostHog
|
||||||
*/
|
*/
|
||||||
export class PosthogSpanExporter implements SpanExporter {
|
export class PosthogSpanExporter implements SpanExporter {
|
||||||
/**
|
/**
|
||||||
|
|
@ -33,41 +50,68 @@ export class PosthogSpanExporter implements SpanExporter {
|
||||||
spans: ReadableSpan[],
|
spans: ReadableSpan[],
|
||||||
resultCallback: (result: ExportResult) => void
|
resultCallback: (result: ExportResult) => void
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
console.log("POSTHOGEXPORTER", spans);
|
await Promise.all(
|
||||||
for (const span of spans) {
|
spans.map((span) => {
|
||||||
const sendInstantly = [
|
switch (span.name) {
|
||||||
"otel_callEnded",
|
case "matrix.groupCallMembership":
|
||||||
"otel_otherSentInstantlyEventName",
|
return this.exportGroupCallMembershipSpan(span);
|
||||||
].includes(span.name);
|
// TBD if there are other spans that we want to process for export to
|
||||||
|
// PostHog
|
||||||
for (const spanEvent of span.events) {
|
|
||||||
await PosthogAnalytics.instance.trackFromSpan(
|
|
||||||
{
|
|
||||||
eventName: spanEvent.name,
|
|
||||||
...spanEvent.attributes,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
send_instantly: sendInstantly,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await PosthogAnalytics.instance.trackFromSpan(
|
|
||||||
{ eventName: span.name, ...span.attributes },
|
|
||||||
{
|
|
||||||
send_instantly: sendInstantly,
|
|
||||||
}
|
}
|
||||||
);
|
})
|
||||||
resultCallback({ code: ExportResultCode.SUCCESS });
|
);
|
||||||
|
|
||||||
|
resultCallback({ code: ExportResultCode.SUCCESS });
|
||||||
|
}
|
||||||
|
|
||||||
|
private get prevCall(): PrevCall | null {
|
||||||
|
// This is stored in localStorage so we can remember the previous call
|
||||||
|
// across app restarts
|
||||||
|
const data = localStorage.getItem("matrix-prev-call");
|
||||||
|
if (data === null) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(data);
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn("Invalid prev call data", data);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private set prevCall(data: PrevCall | null) {
|
||||||
|
localStorage.setItem("matrix-prev-call", JSON.stringify(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
async exportGroupCallMembershipSpan(span: ReadableSpan): Promise<void> {
|
||||||
|
const prevCall = this.prevCall;
|
||||||
|
const newPrevCall = (this.prevCall = {
|
||||||
|
callId: span.attributes["matrix.confId"] as string,
|
||||||
|
hangupTs: hrTimeToMs(span.endTime),
|
||||||
|
});
|
||||||
|
|
||||||
|
// If the user joined the same call within a short time frame, log this as a
|
||||||
|
// rejoin. This is interesting as a call quality metric, since rejoins may
|
||||||
|
// indicate that users had to intervene to make the product work.
|
||||||
|
if (prevCall !== null && newPrevCall.callId === prevCall.callId) {
|
||||||
|
const duration = hrTimeToMs(span.startTime) - prevCall.hangupTs;
|
||||||
|
if (duration <= maxRejoinMs) {
|
||||||
|
PosthogAnalytics.instance.trackEvent(
|
||||||
|
{
|
||||||
|
eventName: "Rejoin",
|
||||||
|
callId: prevCall.callId,
|
||||||
|
rejoinDuration: duration,
|
||||||
|
},
|
||||||
|
// Send instantly because the window might be closing
|
||||||
|
{ send_instantly: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shutdown the exporter.
|
* Shutdown the exporter.
|
||||||
*/
|
*/
|
||||||
shutdown(): Promise<void> {
|
shutdown(): Promise<void> {
|
||||||
console.log("POSTHOGEXPORTER shutdown of otelPosthogExporter");
|
return Promise.resolve();
|
||||||
return new Promise<void>((resolve, _reject) => {
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -385,22 +385,6 @@ export class PosthogAnalytics {
|
||||||
this.capture(eventName, properties, options);
|
this.capture(eventName, properties, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async trackFromSpan(
|
|
||||||
{ eventName, ...properties },
|
|
||||||
options?: CaptureOptions
|
|
||||||
): Promise<void> {
|
|
||||||
if (this.identificationPromise) {
|
|
||||||
// only make calls to posthog after the identificaion is done
|
|
||||||
await this.identificationPromise;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
this.anonymity == Anonymity.Disabled ||
|
|
||||||
this.anonymity == Anonymity.Anonymous
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
this.capture(eventName, properties, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
public startListeningToSettingsChanges(): void {
|
public startListeningToSettingsChanges(): void {
|
||||||
// Listen to account data changes from sync so we can observe changes to relevant flags and update.
|
// Listen to account data changes from sync so we can observe changes to relevant flags and update.
|
||||||
// This is called -
|
// This is called -
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue