Skip to content

Commit

Permalink
chore: report telemetry consent
Browse files Browse the repository at this point in the history
  • Loading branch information
antico5 committed May 2, 2024
1 parent c1f04fc commit 4b5d843
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 26 deletions.
16 changes: 6 additions & 10 deletions client/src/popups/showAnalyticsAllowPopup.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import {
workspace,
window,
ExtensionContext,
ConfigurationTarget,
ExtensionMode,
} from "vscode";
import { workspace, window, ConfigurationTarget, ExtensionMode } from "vscode";
import { ExtensionState } from "../types";

const PREVIOUSLY_SHOWN_TELEMETRY_LABEL = "previouslyShownTelemetryMessage";

export async function showAnalyticsAllowPopup({
context,
}: {
context: ExtensionContext;
}): Promise<void> {
client,
}: ExtensionState): Promise<void> {
if (context.extensionMode === ExtensionMode.Test) {
// Dialog messages are prohibited in tests:
// https://github.com/microsoft/vscode/blob/36fefc828e4c496a7bbb64c63f3eb3052a650f8f/src/vs/workbench/services/dialogs/common/dialogService.ts#L56
Expand Down Expand Up @@ -44,4 +38,6 @@ export async function showAnalyticsAllowPopup({
await config.update("telemetry", isAccepted, ConfigurationTarget.Global);

await context.globalState.update(PREVIOUSLY_SHOWN_TELEMETRY_LABEL, true);

client?.sendNotification("custom/telemetryConsent", isAccepted);
}
59 changes: 57 additions & 2 deletions server/src/analytics/GoogleAnalytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ServerState } from "../types";
import { Analytics, AnalyticsPayload } from "./types";

const GA_URL = "https://www.google-analytics.com/mp/collect";
const TELEMETRY_USER_ID = "hh_vscode_telemetry_consent";

export class GoogleAnalytics implements Analytics {
private readonly measurementID: string;
Expand Down Expand Up @@ -47,7 +48,7 @@ export class GoogleAnalytics implements Analytics {
return;
}

const payload = this._buildPayloadFrom(taskName, this.machineId);
const payload = this._buildTaskPayload(taskName, this.machineId);

await this._sendHit(payload);
} catch {
Expand All @@ -56,7 +57,29 @@ export class GoogleAnalytics implements Analytics {
}
}

private _buildPayloadFrom(
// Meant for the initial response to the telemetry consent popup
public async sendTelemetryResponse(userConsent: boolean): Promise<void> {
try {
const payload = this._buildTelemetryResponsePayload(userConsent);

await this._sendHit(payload);
} catch {
return;
}
}

// Meant for subsequent changes to the telemetry setting
public async sendTelemetryChange(userConsent: boolean): Promise<void> {
try {
const payload = this._buildTelemetryChangePayload(userConsent);

await this._sendHit(payload);
} catch {
return;
}
}

private _buildTaskPayload(
taskName: string,
machineId: string
): AnalyticsPayload {
Expand All @@ -80,6 +103,38 @@ export class GoogleAnalytics implements Analytics {
};
}

private _buildTelemetryResponsePayload(userConsent: boolean) {
return {
client_id: TELEMETRY_USER_ID,
user_id: TELEMETRY_USER_ID,
user_properties: {},
events: [
{
name: "TelemetryConsentResponse",
params: {
userConsent: userConsent ? "yes" : "no",
},
},
],
};
}

private _buildTelemetryChangePayload(userConsent: boolean) {
return {
client_id: TELEMETRY_USER_ID,
user_id: TELEMETRY_USER_ID,
user_properties: {},
events: [
{
name: "TelemetryConsentChange",
params: {
userConsent: userConsent ? "yes" : "no",
},
},
],
};
}

private _sendHit(payload: AnalyticsPayload) {
return got.post(GA_URL, {
headers: {
Expand Down
29 changes: 17 additions & 12 deletions server/src/analytics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,26 @@ export interface AnalyticsPayload {
client_id: string;
user_id: string;

user_properties: {
extensionVersion: {
value?: string;
};
languageClient: {
value?: string;
};
operatingSystem: {
value?: string;
};
};
user_properties:
| {
extensionVersion: {
value?: string;
};
languageClient: {
value?: string;
};
operatingSystem: {
value?: string;
};
}
| {};

events: Array<{
name: string;
params: {
engagement_time_msec: string;
engagement_time_msec?: string;
session_id?: string;
userConsent?: string;
};
}>;
}
Expand All @@ -36,4 +39,6 @@ export interface Analytics {
): void;

sendPageView(taskName: string): Promise<void>;
sendTelemetryResponse(userConsent: boolean): Promise<void>;
sendTelemetryChange(userConsent: boolean): Promise<void>;
}
7 changes: 6 additions & 1 deletion server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,15 @@ function attachCustomHooks(serverState: ServerState) {

connection.onNotification(
"custom/didChangeTelemetryEnabled",
({ enabled }: { enabled: boolean }) => {
async ({ enabled }: { enabled: boolean }) => {
if (enabled) {
logger.info(`Telemetry enabled`);
} else {
logger.info(`Telemetry disabled`);
}

serverState.telemetryEnabled = enabled;
await serverState.telemetry.analytics.sendTelemetryChange(enabled);
}
);

Expand All @@ -126,4 +127,8 @@ function attachCustomHooks(serverState: ServerState) {
serverState.extensionConfig = extensionConfig;
}
);

connection.onNotification("custom/telemetryConsent", async (payload) => {
await serverState.telemetry.analytics.sendTelemetryResponse(payload);
});
}
2 changes: 1 addition & 1 deletion server/src/telemetry/SentryServerTelemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const SENTRY_CLOSE_TIMEOUT = 2000;
export class SentryServerTelemetry implements Telemetry {
private dsn: string;
private serverState: ServerState | null;
private analytics: Analytics;
public analytics: Analytics;
private actionTaken: boolean;
private heartbeatInterval: NodeJS.Timeout | null;
private heartbeatPeriod: number;
Expand Down
3 changes: 3 additions & 0 deletions server/src/telemetry/types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { SpanStatusType } from "@sentry/tracing";
import type { Primitive, Transaction } from "@sentry/types";
import { ServerState } from "../types";
import { Analytics } from "../analytics/types";

export interface TrackingResult<T> {
status: SpanStatusType;
result: T | null;
}

export interface Telemetry {
analytics: Analytics;

init(
machineId: string | undefined,
extensionName: string | undefined,
Expand Down
2 changes: 2 additions & 0 deletions server/test/helpers/setupMockAnalytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ export function setupMockAnalytics(): Analytics {
return {
init: sinon.spy(),
sendPageView: sinon.spy(),
sendTelemetryResponse: sinon.spy(),
sendTelemetryChange: sinon.spy(),
};
}
2 changes: 2 additions & 0 deletions server/test/helpers/setupMockTelemetry.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Transaction } from "@sentry/types";
import * as sinon from "sinon";
import { Telemetry } from "../../src/telemetry/types";
import { setupMockAnalytics } from "./setupMockAnalytics";

export function setupMockTelemetry(): Telemetry {
return {
init: sinon.spy(),
captureException: sinon.spy(),
analytics: setupMockAnalytics(),
trackTiming: async (_taskName: string, action) => {
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down

0 comments on commit 4b5d843

Please sign in to comment.