Skip to content

Commit f90fe48

Browse files
authored
Merge pull request #436 from Achal1607/updated-transform-events
Fixed telemetry event issues
2 parents 78f4858 + 6afb9d8 commit f90fe48

20 files changed

+1050
-105
lines changed

vscode/src/telemetry/events/baseEvent.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
*/
1616
import { LOGGER } from "../../logger";
1717
import { AnonymousIdManager } from "../impl/AnonymousIdManager";
18-
import { cacheService } from "../impl/cacheServiceImpl";
19-
import { getHashCode } from "../utils";
18+
import { cacheServiceIndex } from "../impl/cache";
19+
import { getHashCode, getValuesToBeTransformed, transformValue } from "../utils";
2020

2121
export interface BaseEventPayload {
2222
vsCodeId: string;
@@ -26,6 +26,7 @@ export interface BaseEventPayload {
2626
export abstract class BaseEvent<T> {
2727
protected _payload: T & BaseEventPayload;
2828
protected _data: T
29+
private static readonly blockedValues = getValuesToBeTransformed();
2930

3031
constructor(public readonly NAME: string,
3132
public readonly ENDPOINT: string,
@@ -47,6 +48,13 @@ export abstract class BaseEvent<T> {
4748
return this._data;
4849
}
4950

51+
protected static transformEvent = (propertiesToTransform: string[], payload: Record<string, any>): any => {
52+
const replacedValue = "_REM_";
53+
54+
return transformValue(null, this.blockedValues, propertiesToTransform, replacedValue, payload);
55+
};
56+
57+
5058
public onSuccessPostEventCallback = async (): Promise<void> => {
5159
LOGGER.debug(`${this.NAME} sent successfully`);
5260
}
@@ -58,7 +66,7 @@ export abstract class BaseEvent<T> {
5866
protected addEventToCache = (): void => {
5967
const dataString = JSON.stringify(this.getData);
6068
const calculatedHashVal = getHashCode(dataString);
61-
cacheService.put(this.NAME, calculatedHashVal).then((isAdded: boolean) => {
69+
cacheServiceIndex.simpleCache.put(this.NAME, calculatedHashVal).then((isAdded: boolean) => {
6270
LOGGER.debug(`${this.NAME} added in cache ${isAdded ? "Successfully" : "Unsucessfully"}`);
6371
});
6472
}

vscode/src/telemetry/events/jdkFeature.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ export interface JdkFeatureEventData {
2525
export class JdkFeatureEvent extends BaseEvent<JdkFeatureEventData> {
2626
public static readonly NAME = "jdkFeature";
2727
public static readonly ENDPOINT = "/jdkFeature";
28+
private static readonly propertiesToTransform = ['javaVersion'];
2829

2930
constructor(payload: JdkFeatureEventData) {
30-
super(JdkFeatureEvent.NAME, JdkFeatureEvent.ENDPOINT, payload);
31+
const updatedPayload: JdkFeatureEventData = BaseEvent.transformEvent(JdkFeatureEvent.propertiesToTransform, payload);
32+
super(JdkFeatureEvent.NAME, JdkFeatureEvent.ENDPOINT, updatedPayload);
3133
}
3234

3335
public static concatEvents(events:JdkFeatureEvent[]): JdkFeatureEvent[] {

vscode/src/telemetry/events/start.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
import { globalState } from "../../globalState";
1717
import { LOGGER } from "../../logger";
18-
import { cacheService } from "../impl/cacheServiceImpl";
18+
import { cacheServiceIndex } from "../impl/cache";
1919
import { getEnvironmentInfo } from "../impl/enviromentDetails";
2020
import { getHashCode } from "../utils";
2121
import { BaseEvent } from "./baseEvent";
@@ -53,9 +53,11 @@ export interface StartEventData {
5353
export class ExtensionStartEvent extends BaseEvent<StartEventData> {
5454
public static readonly NAME = "startup";
5555
public static readonly ENDPOINT = "/start";
56+
private static readonly propertiesToTransform = ['osVersion'];
5657

5758
constructor(payload: StartEventData) {
58-
super(ExtensionStartEvent.NAME, ExtensionStartEvent.ENDPOINT, payload);
59+
const updatedPayload: StartEventData = BaseEvent.transformEvent(ExtensionStartEvent.propertiesToTransform, payload);
60+
super(ExtensionStartEvent.NAME, ExtensionStartEvent.ENDPOINT, updatedPayload);
5961
}
6062

6163
onSuccessPostEventCallback = async (): Promise<void> => {
@@ -65,7 +67,7 @@ export class ExtensionStartEvent extends BaseEvent<StartEventData> {
6567

6668
public static builder = (): ExtensionStartEvent | null => {
6769
const startEventData = getEnvironmentInfo(globalState.getExtensionContextInfo());
68-
const cachedValue: string | undefined = cacheService.get(this.NAME);
70+
const cachedValue: string | undefined = cacheServiceIndex.simpleCache.get(this.NAME);
6971
const envString = JSON.stringify(startEventData);
7072
const newValue = getHashCode(envString);
7173

vscode/src/telemetry/events/workspaceChange.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313
See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
16+
import { randomUUID } from "crypto";
1617
import { LOGGER } from "../../logger";
1718
import { Telemetry } from "../telemetry";
1819
import { BaseEvent } from "./baseEvent";
20+
import { cacheServiceIndex } from "../impl/cache";
21+
import { ProjectCacheValue } from "../impl/cache/projectCacheValue";
1922

2023
interface ProjectInfo {
2124
id: string;
@@ -37,9 +40,32 @@ let workspaceChangeEventTimeout: NodeJS.Timeout | null = null;
3740
export class WorkspaceChangeEvent extends BaseEvent<WorkspaceChangeData> {
3841
public static readonly NAME = "workspaceChange";
3942
public static readonly ENDPOINT = "/workspaceChange";
43+
private static readonly propertiesToTransform = ['javaVersion'];
4044

4145
constructor(payload: WorkspaceChangeData) {
42-
super(WorkspaceChangeEvent.NAME, WorkspaceChangeEvent.ENDPOINT, payload);
46+
const updatedPayload: WorkspaceChangeData = WorkspaceChangeEvent.transformPayload(payload);
47+
super(WorkspaceChangeEvent.NAME, WorkspaceChangeEvent.ENDPOINT, updatedPayload);
48+
}
49+
50+
private static transformPayload = (payload: WorkspaceChangeData) => {
51+
const transformedPayload: WorkspaceChangeData = BaseEvent.transformEvent(WorkspaceChangeEvent.propertiesToTransform, payload);
52+
return WorkspaceChangeEvent.updateProjectId(transformedPayload)
53+
}
54+
55+
private static updateProjectId = (payload: WorkspaceChangeData) => {
56+
const updatedProjectInfo = payload.projectInfo.map(project => {
57+
const existingId = cacheServiceIndex.projectCache.get(project.id);
58+
const uniqueId = existingId ?? randomUUID();
59+
60+
if (!existingId) {
61+
// Cannot be awaited because the caller is constructor and it cannot be a async call
62+
cacheServiceIndex.projectCache.put(project.id, new ProjectCacheValue(uniqueId));
63+
}
64+
65+
return { ...project, id: uniqueId };
66+
});
67+
68+
return { ...payload, projectInfo: updatedProjectInfo };
4369
}
4470

4571
public onSuccessPostEventCallback = async (): Promise<void> => {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
Copyright (c) 2025, Oracle and/or its affiliates.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
export type CacheValueObj<T> = {
18+
type: string;
19+
payload: T;
20+
lastUsed: number;
21+
}
22+
23+
export abstract class BaseCacheValue<T> {
24+
public readonly lastUsed: number;
25+
26+
constructor(public readonly type: string, public readonly payload: T, lastUsed?: number) {
27+
this.lastUsed = lastUsed ?? Date.now();
28+
}
29+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
Copyright (c) 2025, Oracle and/or its affiliates.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { ProjectCacheService } from "./projectCacheService";
18+
import { SimpleCacheService } from "./simpleCacheService";
19+
20+
export namespace cacheServiceIndex {
21+
export const simpleCache = new SimpleCacheService();
22+
export const projectCache = new ProjectCacheService();
23+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
Copyright (c) 2024-2025, Oracle and/or its affiliates.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
import { CacheService } from "../../types";
17+
import { LOGGER } from "../../../logger";
18+
import { globalState } from "../../../globalState";
19+
import { isError } from "../../../utils";
20+
import { removeEntriesOnOverflow } from "./utils";
21+
import { ProjectCacheValue } from "./projectCacheValue";
22+
23+
export class ProjectCacheService implements CacheService<ProjectCacheValue, String> {
24+
readonly MAX_KEYS_SIZE: number = 5000;
25+
private removingKeys: boolean = false;
26+
27+
public get = (key: string) => {
28+
try {
29+
const updatedKey = this.getUpdatedKey(key);
30+
const vscGlobalState = globalState.getExtensionContextInfo().getVscGlobalState();
31+
32+
const value = vscGlobalState.get<ProjectCacheValue>(updatedKey);
33+
if (value) {
34+
this.put(updatedKey, ProjectCacheValue.fromObject({ ...value, lastUsed: Date.now() }));
35+
}
36+
37+
return value?.payload;
38+
} catch (err) {
39+
LOGGER.error(`Error while retrieving ${key} from cache: ${(err as Error).message}`);
40+
return undefined;
41+
}
42+
}
43+
44+
public put = async (key: string, value: ProjectCacheValue) => {
45+
try {
46+
const updatedKey = this.getUpdatedKey(key);
47+
const vscGlobalState = globalState.getExtensionContextInfo().getVscGlobalState();
48+
49+
await vscGlobalState.update(updatedKey, value);
50+
if (vscGlobalState.keys().length > this.MAX_KEYS_SIZE) {
51+
this.removeOnOverflow();
52+
}
53+
LOGGER.debug(`Updating key: ${key} to ${value}`);
54+
55+
return true;
56+
} catch (err) {
57+
LOGGER.error(`Error while storing ${key} in cache: ${(err as Error).message}`);
58+
return false;
59+
}
60+
}
61+
62+
public removeOnOverflow = async () => {
63+
try {
64+
if (this.removingKeys) {
65+
LOGGER.log("Ignoring removing keys request, since it is already in progress");
66+
return;
67+
}
68+
this.removingKeys = true;
69+
70+
const vscGlobalState = globalState.getExtensionContextInfo().getVscGlobalState();
71+
const comparator = (a: ProjectCacheValue, b: ProjectCacheValue) => (a.lastUsed - b.lastUsed);
72+
73+
await removeEntriesOnOverflow(vscGlobalState, ProjectCacheValue.type, comparator);
74+
} catch (error) {
75+
LOGGER.error("Some error occurred while removing keys " + (isError(error) ? error.message : error));
76+
} finally {
77+
this.removingKeys = false;
78+
}
79+
}
80+
81+
// for unit tests needs to be public
82+
public getUpdatedKey = (key: string) => `${ProjectCacheValue.type}.${key}`;
83+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Copyright (c) 2025, Oracle and/or its affiliates.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { BaseCacheValue, CacheValueObj } from "./BaseCacheValue";
18+
19+
export class ProjectCacheValue extends BaseCacheValue<string> {
20+
public static readonly type = "prjId";
21+
22+
constructor(payload: string, lastUsed?: number){
23+
super(ProjectCacheValue.type, payload, lastUsed);
24+
}
25+
26+
public static fromObject(obj: CacheValueObj<string>): ProjectCacheValue {
27+
if (obj.type !== ProjectCacheValue.type) {
28+
throw new Error(`Invalid object type for ProjectCacheEntry: received ${obj.type}`);
29+
}
30+
const entry = new ProjectCacheValue(obj.payload, obj.lastUsed);
31+
32+
return entry;
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright (c) 2024-2025, Oracle and/or its affiliates.
2+
Copyright (c) 2025, Oracle and/or its affiliates.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -13,32 +13,32 @@
1313
See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
16-
import { CacheService } from "../types";
17-
import { LOGGER } from "../../logger";
18-
import { globalState } from "../../globalState";
1916

20-
class CacheServiceImpl implements CacheService {
21-
public get = (key: string): string | undefined => {
17+
import { globalState } from "../../../globalState";
18+
import { LOGGER } from "../../../logger";
19+
import { CacheService } from "../../types";
20+
21+
export class SimpleCacheService implements CacheService<string, string> {
22+
get(key: string) {
2223
try {
2324
const vscGlobalState = globalState.getExtensionContextInfo().getVscGlobalState();
24-
return vscGlobalState.get(key);
25+
return vscGlobalState.get<string>(key);
2526
} catch (err) {
2627
LOGGER.error(`Error while retrieving ${key} from cache: ${(err as Error).message}`);
2728
return undefined;
2829
}
2930
}
30-
31-
public put = async (key: string, value: string): Promise<boolean> => {
31+
32+
async put(key: string, value: string) {
3233
try {
3334
const vscGlobalState = globalState.getExtensionContextInfo().getVscGlobalState();
3435
await vscGlobalState.update(key, value);
3536
LOGGER.debug(`Updating key: ${key} to ${value}`);
37+
3638
return true;
3739
} catch (err) {
3840
LOGGER.error(`Error while storing ${key} in cache: ${(err as Error).message}`);
3941
return false;
4042
}
4143
}
4244
}
43-
44-
export const cacheService = new CacheServiceImpl();

0 commit comments

Comments
 (0)