Skip to content

Commit aaed043

Browse files
authored
Git repo cloud telemetry (#5119)
This PR adds filtering of git repository properties from telemetry data and includes git info in telemetry properties, with comprehensive tests. Behavior: PostHogTelemetryClient now filters out repositoryUrl, repositoryName, and defaultBranch from telemetry events. ClineProvider includes git repository information in telemetry properties, filtered by clients. Functions: Added isPropertyCapturable() in BaseTelemetryClient to allow property filtering. Implemented getGitRepositoryInfo() and getWorkspaceGitInfo() in git.ts to extract git info. Tests: Added tests for isPropertyCapturable() in PostHogTelemetryClient.test.ts. Added tests for getGitRepositoryInfo() and getWorkspaceGitInfo() in git.spec.ts.
1 parent 83c19ce commit aaed043

File tree

7 files changed

+647
-17
lines changed

7 files changed

+647
-17
lines changed

packages/telemetry/src/BaseTelemetryClient.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,21 @@ export abstract class BaseTelemetryClient implements TelemetryClient {
2525
: !this.subscription.events.includes(eventName)
2626
}
2727

28+
/**
29+
* Determines if a specific property should be included in telemetry events
30+
* Override in subclasses to filter specific properties
31+
*/
32+
protected isPropertyCapturable(_propertyName: string): boolean {
33+
return true
34+
}
35+
2836
protected async getEventProperties(event: TelemetryEvent): Promise<TelemetryEvent["properties"]> {
2937
let providerProperties: TelemetryEvent["properties"] = {}
3038
const provider = this.providerRef?.deref()
3139

3240
if (provider) {
3341
try {
34-
// Get the telemetry properties directly from the provider.
42+
// Get properties from the provider
3543
providerProperties = await provider.getTelemetryProperties()
3644
} catch (error) {
3745
// Log error but continue with capturing the event.
@@ -43,7 +51,10 @@ export abstract class BaseTelemetryClient implements TelemetryClient {
4351

4452
// Merge provider properties with event-specific properties.
4553
// Event properties take precedence in case of conflicts.
46-
return { ...providerProperties, ...(event.properties || {}) }
54+
const mergedProperties = { ...providerProperties, ...(event.properties || {}) }
55+
56+
// Filter out properties that shouldn't be captured by this client
57+
return Object.fromEntries(Object.entries(mergedProperties).filter(([key]) => this.isPropertyCapturable(key)))
4758
}
4859

4960
public abstract capture(event: TelemetryEvent): Promise<void>

packages/telemetry/src/PostHogTelemetryClient.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { BaseTelemetryClient } from "./BaseTelemetryClient"
1313
export class PostHogTelemetryClient extends BaseTelemetryClient {
1414
private client: PostHog
1515
private distinctId: string = vscode.env.machineId
16+
// Git repository properties that should be filtered out
17+
private readonly gitPropertyNames = ["repositoryUrl", "repositoryName", "defaultBranch"]
1618

1719
constructor(debug = false) {
1820
super(
@@ -26,6 +28,19 @@ export class PostHogTelemetryClient extends BaseTelemetryClient {
2628
this.client = new PostHog(process.env.POSTHOG_API_KEY || "", { host: "https://us.i.posthog.com" })
2729
}
2830

31+
/**
32+
* Filter out git repository properties for PostHog telemetry
33+
* @param propertyName The property name to check
34+
* @returns Whether the property should be included in telemetry events
35+
*/
36+
protected override isPropertyCapturable(propertyName: string): boolean {
37+
// Filter out git repository properties
38+
if (this.gitPropertyNames.includes(propertyName)) {
39+
return false
40+
}
41+
return true
42+
}
43+
2944
public override async capture(event: TelemetryEvent): Promise<void> {
3045
if (!this.isTelemetryEnabled() || !this.isEventCapturable(event.event)) {
3146
if (this.debug) {

packages/telemetry/src/__tests__/PostHogTelemetryClient.test.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,29 @@ describe("PostHogTelemetryClient", () => {
7070
})
7171
})
7272

73+
describe("isPropertyCapturable", () => {
74+
it("should filter out git repository properties", () => {
75+
const client = new PostHogTelemetryClient()
76+
77+
const isPropertyCapturable = getPrivateProperty<(propertyName: string) => boolean>(
78+
client,
79+
"isPropertyCapturable",
80+
).bind(client)
81+
82+
// Git properties should be filtered out
83+
expect(isPropertyCapturable("repositoryUrl")).toBe(false)
84+
expect(isPropertyCapturable("repositoryName")).toBe(false)
85+
expect(isPropertyCapturable("defaultBranch")).toBe(false)
86+
87+
// Other properties should be included
88+
expect(isPropertyCapturable("appVersion")).toBe(true)
89+
expect(isPropertyCapturable("vscodeVersion")).toBe(true)
90+
expect(isPropertyCapturable("platform")).toBe(true)
91+
expect(isPropertyCapturable("mode")).toBe(true)
92+
expect(isPropertyCapturable("customProperty")).toBe(true)
93+
})
94+
})
95+
7396
describe("getEventProperties", () => {
7497
it("should merge provider properties with event properties", async () => {
7598
const client = new PostHogTelemetryClient()
@@ -112,6 +135,54 @@ describe("PostHogTelemetryClient", () => {
112135
expect(mockProvider.getTelemetryProperties).toHaveBeenCalledTimes(1)
113136
})
114137

138+
it("should filter out git repository properties", async () => {
139+
const client = new PostHogTelemetryClient()
140+
141+
const mockProvider: TelemetryPropertiesProvider = {
142+
getTelemetryProperties: vi.fn().mockResolvedValue({
143+
appVersion: "1.0.0",
144+
vscodeVersion: "1.60.0",
145+
platform: "darwin",
146+
editorName: "vscode",
147+
language: "en",
148+
mode: "code",
149+
// Git properties that should be filtered out
150+
repositoryUrl: "https://github.com/example/repo",
151+
repositoryName: "example/repo",
152+
defaultBranch: "main",
153+
}),
154+
}
155+
156+
client.setProvider(mockProvider)
157+
158+
const getEventProperties = getPrivateProperty<
159+
(event: { event: TelemetryEventName; properties?: Record<string, any> }) => Promise<Record<string, any>>
160+
>(client, "getEventProperties").bind(client)
161+
162+
const result = await getEventProperties({
163+
event: TelemetryEventName.TASK_CREATED,
164+
properties: {
165+
customProp: "value",
166+
},
167+
})
168+
169+
// Git properties should be filtered out
170+
expect(result).not.toHaveProperty("repositoryUrl")
171+
expect(result).not.toHaveProperty("repositoryName")
172+
expect(result).not.toHaveProperty("defaultBranch")
173+
174+
// Other properties should be included
175+
expect(result).toEqual({
176+
appVersion: "1.0.0",
177+
vscodeVersion: "1.60.0",
178+
platform: "darwin",
179+
editorName: "vscode",
180+
language: "en",
181+
mode: "code",
182+
customProp: "value",
183+
})
184+
})
185+
115186
it("should handle errors from provider gracefully", async () => {
116187
const client = new PostHogTelemetryClient()
117188

@@ -211,6 +282,48 @@ describe("PostHogTelemetryClient", () => {
211282
}),
212283
})
213284
})
285+
286+
it("should filter out git repository properties when capturing events", async () => {
287+
const client = new PostHogTelemetryClient()
288+
client.updateTelemetryState(true)
289+
290+
const mockProvider: TelemetryPropertiesProvider = {
291+
getTelemetryProperties: vi.fn().mockResolvedValue({
292+
appVersion: "1.0.0",
293+
vscodeVersion: "1.60.0",
294+
platform: "darwin",
295+
editorName: "vscode",
296+
language: "en",
297+
mode: "code",
298+
// Git properties that should be filtered out
299+
repositoryUrl: "https://github.com/example/repo",
300+
repositoryName: "example/repo",
301+
defaultBranch: "main",
302+
}),
303+
}
304+
305+
client.setProvider(mockProvider)
306+
307+
await client.capture({
308+
event: TelemetryEventName.TASK_CREATED,
309+
properties: { test: "value" },
310+
})
311+
312+
expect(mockPostHogClient.capture).toHaveBeenCalledWith({
313+
distinctId: "test-machine-id",
314+
event: TelemetryEventName.TASK_CREATED,
315+
properties: expect.objectContaining({
316+
appVersion: "1.0.0",
317+
test: "value",
318+
}),
319+
})
320+
321+
// Verify git properties are not included
322+
const captureCall = mockPostHogClient.capture.mock.calls[0][0]
323+
expect(captureCall.properties).not.toHaveProperty("repositoryUrl")
324+
expect(captureCall.properties).not.toHaveProperty("repositoryName")
325+
expect(captureCall.properties).not.toHaveProperty("defaultBranch")
326+
})
214327
})
215328

216329
describe("updateTelemetryState", () => {

packages/types/src/telemetry.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,7 @@ export const telemetryPropertiesSchema = z.object({
101101
...gitPropertiesSchema.shape,
102102
})
103103

104-
export const cloudTelemetryPropertiesSchema = z.object({
105-
...telemetryPropertiesSchema.shape,
106-
})
107-
108104
export type TelemetryProperties = z.infer<typeof telemetryPropertiesSchema>
109-
export type CloudTelemetryProperties = z.infer<typeof cloudTelemetryPropertiesSchema>
110105
export type GitProperties = z.infer<typeof gitPropertiesSchema>
111106

112107
/**
@@ -161,20 +156,20 @@ export const rooCodeTelemetryEventSchema = z.discriminatedUnion("type", [
161156
TelemetryEventName.MODE_SETTINGS_CHANGED,
162157
TelemetryEventName.CUSTOM_MODE_CREATED,
163158
]),
164-
properties: cloudTelemetryPropertiesSchema,
159+
properties: telemetryPropertiesSchema,
165160
}),
166161
z.object({
167162
type: z.literal(TelemetryEventName.TASK_MESSAGE),
168163
properties: z.object({
169-
...cloudTelemetryPropertiesSchema.shape,
164+
...telemetryPropertiesSchema.shape,
170165
taskId: z.string(),
171166
message: clineMessageSchema,
172167
}),
173168
}),
174169
z.object({
175170
type: z.literal(TelemetryEventName.LLM_COMPLETION),
176171
properties: z.object({
177-
...cloudTelemetryPropertiesSchema.shape,
172+
...telemetryPropertiesSchema.shape,
178173
inputTokens: z.number(),
179174
outputTokens: z.number(),
180175
cacheReadTokens: z.number().optional(),
@@ -200,7 +195,6 @@ export type TelemetryEventSubscription =
200195

201196
export interface TelemetryPropertiesProvider {
202197
getTelemetryProperties(): Promise<TelemetryProperties>
203-
getCloudTelemetryProperties?(): Promise<CloudTelemetryProperties>
204198
}
205199

206200
/**

src/core/webview/ClineProvider.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import { webviewMessageHandler } from "./webviewMessageHandler"
6868
import { WebviewMessage } from "../../shared/WebviewMessage"
6969
import { EMBEDDING_MODEL_PROFILES } from "../../shared/embeddingModels"
7070
import { ProfileValidator } from "../../shared/ProfileValidator"
71+
import { getWorkspaceGitInfo } from "../../utils/git"
7172

7273
/**
7374
* https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@@ -1785,7 +1786,7 @@ export class ClineProvider
17851786
/**
17861787
* Returns properties to be included in every telemetry event
17871788
* This method is called by the telemetry service to get context information
1788-
* like the current mode, API provider, etc.
1789+
* like the current mode, API provider, git repository information, etc.
17891790
*/
17901791
public async getTelemetryProperties(): Promise<TelemetryProperties> {
17911792
const { mode, apiConfiguration, language } = await this.getState()
@@ -1805,6 +1806,10 @@ export class ClineProvider
18051806
this.log(`[getTelemetryProperties] Failed to get cloud auth state: ${error}`)
18061807
}
18071808

1809+
// Get git repository information
1810+
const gitInfo = await getWorkspaceGitInfo()
1811+
1812+
// Return all properties including git info - clients will filter as needed
18081813
return {
18091814
appName: packageJSON?.name ?? Package.name,
18101815
appVersion: packageJSON?.version ?? Package.version,
@@ -1818,6 +1823,7 @@ export class ClineProvider
18181823
diffStrategy: task?.diffStrategy?.getName(),
18191824
isSubtask: task ? !!task.parentTask : undefined,
18201825
cloudIsAuthenticated,
1826+
...gitInfo,
18211827
}
18221828
}
18231829
}

0 commit comments

Comments
 (0)