-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: send plugin installed event on startup [IDE-736] (#556)
### Description - add analytics sending on startup ### Checklist - [x] Tests added and all succeed - [x] Linted - [x] CHANGELOG.md updated - [ ] README.md updated, if user-facing ### Screenshots / GIFs _Visuals that may help the reviewer. Please add screenshots for any UI change. GIFs are most welcome!_
- Loading branch information
Showing
24 changed files
with
205 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { AbstractAnalyticsEvent } from './AnalyticsSender'; | ||
|
||
export class AnalyticsEvent implements AbstractAnalyticsEvent { | ||
private readonly interactionType: string; | ||
private readonly category: string[]; | ||
private readonly status: string; | ||
private readonly targetId: string; | ||
private readonly timestampMs: number; | ||
private readonly durationMs: number; | ||
private readonly results: Map<string, unknown>; | ||
private readonly errors: unknown[]; | ||
private readonly extension: Map<string, unknown>; | ||
|
||
constructor( | ||
deviceId: string, | ||
interactionType: string, | ||
category: string[], | ||
status: string = 'success', | ||
targetId: string = 'pkg:filesystem/scrubbed', | ||
timestampMs: number = Date.now(), | ||
durationMs: number = 0, | ||
results: Map<string, unknown> = new Map<string, unknown>(), | ||
errors: unknown[] = [], | ||
extension: Map<string, unknown> = new Map<string, unknown>(), | ||
) { | ||
this.interactionType = interactionType; | ||
this.category = category; | ||
this.status = status ?? 'success'; | ||
this.targetId = targetId ?? 'pkg:filesystem/scrubbed'; | ||
this.timestampMs = timestampMs ?? Date.now(); | ||
this.durationMs = durationMs ?? 0; | ||
this.results = results ?? new Map<string, unknown>(); | ||
this.errors = errors ?? []; | ||
this.extension = extension ?? new Map<string, unknown>(); | ||
if (deviceId && deviceId.length > 0) { | ||
this.extension.set('device_id', deviceId); | ||
} | ||
} | ||
|
||
public getInteractionType(): string { | ||
return this.interactionType; | ||
} | ||
|
||
public getCategory(): string[] { | ||
return this.category; | ||
} | ||
|
||
public getStatus(): string { | ||
return this.status; | ||
} | ||
|
||
public getTargetId(): string { | ||
return this.targetId; | ||
} | ||
|
||
public getTimestampMs(): number { | ||
return this.timestampMs; | ||
} | ||
|
||
public getDurationMs(): number { | ||
return this.durationMs; | ||
} | ||
|
||
public getResults(): Map<string, unknown> { | ||
return this.results; | ||
} | ||
|
||
public getErrors(): unknown[] { | ||
return this.errors; | ||
} | ||
|
||
public getExtension(): Map<string, unknown> { | ||
return this.extension; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// noinspection InfiniteLoopJS | ||
|
||
import { ILog } from '../logger/interfaces'; | ||
import { IConfiguration } from '../configuration/configuration'; | ||
import { sleep } from '@amplitude/experiment-node-server/dist/src/util/time'; | ||
import { IVSCodeCommands } from '../vscode/commands'; | ||
import { SNYK_REPORT_ANALYTICS } from '../constants/commands'; | ||
import { IContextService } from '../services/contextService'; | ||
import { SNYK_CONTEXT } from '../constants/views'; | ||
|
||
interface EventPair { | ||
event: AbstractAnalyticsEvent; | ||
callback: (value: void) => void; | ||
} | ||
|
||
// This is just a marker interface, to ensure type security when sending events | ||
export interface AbstractAnalyticsEvent {} | ||
|
||
export class AnalyticsSender { | ||
private static instance: AnalyticsSender; | ||
private eventQueue: EventPair[] = []; | ||
|
||
constructor( | ||
private logger: ILog, | ||
private configuration: IConfiguration, | ||
private commandExecutor: IVSCodeCommands, | ||
private contextService: IContextService, | ||
) { | ||
void this.start(); | ||
} | ||
|
||
public static getInstance( | ||
logger: ILog, | ||
configuration: IConfiguration, | ||
commandExecutor: IVSCodeCommands, | ||
contextService: IContextService, | ||
): AnalyticsSender { | ||
if (!AnalyticsSender.instance) { | ||
AnalyticsSender.instance = new AnalyticsSender(logger, configuration, commandExecutor, contextService); | ||
} | ||
return AnalyticsSender.instance; | ||
} | ||
|
||
private async start(): Promise<void> { | ||
// eslint-disable-next-line no-constant-condition | ||
while (true) { | ||
// eslint-disable-next-line no-await-in-loop | ||
const authToken = await this.configuration.getToken(); | ||
const initialized: boolean = (this.contextService.viewContext[SNYK_CONTEXT.INITIALIZED] as boolean) ?? false; | ||
const hasEvents = this.eventQueue.length > 0; | ||
const authenticated = authToken && authToken.trim() !== ''; | ||
const iAmTired = !(initialized && authenticated && hasEvents); | ||
|
||
if (iAmTired) { | ||
// eslint-disable-next-line no-await-in-loop | ||
await sleep(5000); | ||
continue; | ||
} | ||
|
||
const copyForSending = [...this.eventQueue]; | ||
for (let i = 0; i < copyForSending.length; i++) { | ||
const eventPair = copyForSending[i]; | ||
try { | ||
// eslint-disable-next-line no-await-in-loop | ||
await this.commandExecutor.executeCommand(SNYK_REPORT_ANALYTICS, JSON.stringify(eventPair.event)); | ||
eventPair.callback(); | ||
} catch (error) { | ||
// eslint-disable-next-line @typescript-eslint/no-base-to-string | ||
this.logger.error(`could not send ${eventPair.event} ${error}`); | ||
} finally { | ||
// let's not rely on indexes in the eventQueue array not having changed | ||
const index = this.eventQueue.indexOf(eventPair); | ||
if (index > -1) { | ||
this.eventQueue.splice(index, 1); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
public logEvent(event: AbstractAnalyticsEvent, callback: (value: void) => void): void { | ||
this.eventQueue.push({ event, callback }); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.