Skip to content

Commit

Permalink
Add basic telemetry to track anonymous usage (#48)
Browse files Browse the repository at this point in the history
* Add basic telemetry to track anonymous usage

* Use generic type

Co-authored-by: Alex Khomenko <[email protected]>

* Better date handling

* Switch to daily post

* PR feedback

* Fix logic (thanks Dan)

* Logging, and add timestamp

* Adjust post body to usageStats needs

* Adjust post body to usageStats needs

* Fix telemetry url and add opt-out

* Share number of 'views'

* Correct comment

---------

Co-authored-by: Alex Khomenko <[email protected]>
  • Loading branch information
malcolmholmes and Clarity-89 authored Jan 5, 2024
1 parent 6c9a905 commit 96dc2e7
Show file tree
Hide file tree
Showing 6 changed files with 2,828 additions and 4,303 deletions.
16 changes: 12 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"version": "0.0.12",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/grafana/grafana-vs-code-extension"
"type": "git",
"url": "https://github.com/grafana/grafana-vs-code-extension"
},
"engines": {
"vscode": "^1.76.0"
Expand Down Expand Up @@ -77,7 +77,13 @@
"type": "boolean",
"default": true,
"markdownDescription": "A service account token for your Grafana instance. Click the link below to add this to secure storage.\n\n[Set your token, securely](command:grafana-vscode.setPassword)",
"order":2
"order": 2
},
"grafana-vscode.telemetry": {
"type": "boolean",
"default": true,
"markdownDescription": "Enable basic telemetry. All data is anonymous and only used to help with feature prioritization/gloating/etc.",
"order": 3
}
}
}
Expand Down Expand Up @@ -115,13 +121,15 @@
"webpack-cli": "^5.0.1"
},
"dependencies": {
"@types/uuid": "^9.0.6",
"axios": "^1.4.0",
"cors": "^2.8.5",
"express": "^4.18.2",
"http-proxy": "^1.18.1",
"http-proxy-middleware": "^2.0.6",
"open": "^8.4.2",
"source-map-support": "^0.5.21",
"transformer-proxy": "^0.3.5"
"transformer-proxy": "^0.3.5",
"uuid": "^9.0.1"
}
}
5 changes: 4 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from "vscode";
import { setVersion, startServer, restartServer, stopServer, TOKEN_SECRET } from "./server";
import { startServer, restartServer, stopServer, TOKEN_SECRET } from "./server";
import { GrafanaEditorProvider } from "./editor";
import { install as installSourceMapSupport } from 'source-map-support';
import { sendTelemetry } from "./telemetry";
import { setVersion } from "./util";

// This method is called when your extension is activated
// Your extension is activated the very first time the command is executed
Expand All @@ -18,6 +20,7 @@ export async function activate(ctx: vscode.ExtensionContext) {
vscode.commands.registerCommand(
"grafana-vscode.openUrl",
(uri: vscode.Uri) => {
sendTelemetry(ctx);
vscode.commands.executeCommand(
"vscode.openWith",
uri,
Expand Down
11 changes: 3 additions & 8 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,14 @@ import * as cors from "cors";
import { detectRequestSource } from "./middleware";
import axios from "axios";
import * as path from "path";
import * as util from "./util";

export let port = 0;

let server: Server;

let userAgent: string;

export const TOKEN_SECRET = "grafana-vscode.token";

export function setVersion(version: string) {
userAgent = `Grafana VSCode Extension/v${version}`;
}

export async function startServer(secrets: vscode.SecretStorage, extensionPath: string) {
const settings = vscode.workspace.getConfiguration("grafana-vscode");
const URL = String(settings.get("URL"));
Expand All @@ -42,7 +37,7 @@ export async function startServer(secrets: vscode.SecretStorage, extensionPath:
// eslint-disable-next-line @typescript-eslint/naming-convention
Authorization: `Bearer ${token}`,
// eslint-disable-next-line @typescript-eslint/naming-convention
'User-Agent': userAgent,
'User-Agent': util.getUserAgent(),
},
});

Expand Down Expand Up @@ -81,7 +76,7 @@ export async function startServer(secrets: vscode.SecretStorage, extensionPath:
// eslint-disable-next-line @typescript-eslint/naming-convention
Authorization: `Bearer ${token}`,
// eslint-disable-next-line @typescript-eslint/naming-convention
'User-Agent': userAgent,
'User-Agent': util.getUserAgent(),
},
});
res.write(resp.data);
Expand Down
88 changes: 88 additions & 0 deletions src/telemetry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import * as vscode from "vscode";
import axios from "axios";
import {v4 as uuidv4} from 'uuid';
import * as util from "./util";

const LAST_UPDATED_DATE = "lastUpdatedDate";
const INSTALLATION_DATE = "installDate";
const INSTALLATION_UUID = "installUUID";
const RECENT_VIEWS = "recentViews";

const URL = "https://stats.grafana.org/vscode-usage-report";

/*
* Sends a single anonymous telemetry call once per day, allowing tracking of
* usage - reports on first opening of a dashboard each day.
*/
export async function sendTelemetry(ctx: vscode.ExtensionContext) {

const settings = vscode.workspace.getConfiguration("grafana-vscode");
const enableTelemetry = settings.get<boolean>("telemetry");
if (!enableTelemetry) {
return;
}
const lastUpdatedDate = ctx.globalState.get<string | undefined>(LAST_UPDATED_DATE);
const today = new Date();

if (lastUpdatedDate === undefined) {
const uuid = uuidv4();
await sendEvent("first", uuid, today.toISOString(), 1);
ctx.globalState.update(LAST_UPDATED_DATE, today);
ctx.globalState.update(INSTALLATION_UUID, uuid);
ctx.globalState.update(INSTALLATION_DATE, today);
ctx.globalState.update(RECENT_VIEWS, 0);
} else {
let recentViews = ctx.globalState.get<number | undefined>(RECENT_VIEWS);
recentViews = (recentViews === undefined) ? 1 : recentViews+1;

if (differentDay(new Date(lastUpdatedDate), today)) {
let uuid = ctx.globalState.get(INSTALLATION_UUID);
let installDate = ctx.globalState.get(INSTALLATION_DATE);
if (uuid === undefined) {
console.log("UUID undefined. Shouldn't happen.");
uuid = uuidv4();
ctx.globalState.update(INSTALLATION_UUID, uuid);
}
if (installDate === undefined) {
console.log("Install date undefined. Shouldn't happen.");
installDate = (new Date(lastUpdatedDate)).toISOString();
ctx.globalState.update(INSTALLATION_DATE, installDate);
}
await sendEvent("subsequent", uuid as string, installDate as string, recentViews);
ctx.globalState.update(LAST_UPDATED_DATE, today);
recentViews = 0;
}
ctx.globalState.update(RECENT_VIEWS, recentViews);
}
}

function differentDay(d1: Date, d2: Date) {
return d1.getDate() !== d2.getDate() ||
d1.getMonth() !== d2.getMonth() ||
d1.getFullYear() !== d2.getFullYear();
}

async function sendEvent(eventType: string, uuid: string, installDate: string, views: number | undefined) {
try {
const data = {
uuid: uuid,
eventType: eventType,
timestamp: Date(),
createdAt: installDate,
os: process.platform,
arch: process.arch,
packaging: "unknown",
views: views,
version: util.getVersion(),
};

await axios.post(URL, data, {
headers: {
// eslint-disable-next-line @typescript-eslint/naming-convention
'User-Agent': util.getUserAgent(),
},
});
} catch(e) {
console.log("Telemetry error", e, "for event", eventType);
}
}
22 changes: 14 additions & 8 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
export function getNonce() {
let text = "";
const possible =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
let userAgent: string;
let version: string;

export function setVersion(v: string) {
version = v;
userAgent = `Grafana VSCode Extension/v${version}`;
}

export function getVersion(): string {
return version;
}

export function getUserAgent(): string {
return userAgent;
}
Loading

0 comments on commit 96dc2e7

Please sign in to comment.