Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add Rudderstack for event tracking #46

Merged
merged 1 commit into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/runTests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,24 @@ on:
jobs:
test:
name: Test
runs-on: windows-latest
runs-on: ubuntu-latest
env:
DISPLAY: ':99.0'
steps:
- name: Start xvfb
run: /usr/bin/Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & echo ">>> Started xvfb"

- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16.x'
cache: 'yarn'

- name: Install dependencies
run: yarn --immutable && yarn install

- name: Lint Project
run: yarn lint

- name: Test Project
run: yarn test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ node_modules
.env
.idea
.DS_STORE
/src/analytics.ts
20 changes: 15 additions & 5 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,32 @@
"version": "2.0.0",
"tasks": [
{
"label": "watch",
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"type": "npm",
"script": "compile",
"problemMatcher": []
"problemMatcher": [],
},
{
"label": "create-analytics-file",
"type": "npm",
"script": "create-analytics-file",
},
{
"label": "dev",
"dependsOn": ["create-analytics-file", "watch"],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
12 changes: 12 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,15 @@
- Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension).
- [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace.
- Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration).

## [Internal] RudderStack setup

- In order to track events during development, you will need to get an authorization token from RudderStack.

1. Go to [RudderStack](https://app.rudderstack.com/) and login with the credentials stored in 1Password
2. Go to `Sources` and select the `VS Code Extension` source
3. On the `Setup` tab, copy the `Write Key`
4. Go to a [Basic Authentication Header Generator
](https://www.blitter.se/utils/basic-authentication-header-generator/) to generate a token.
5. Use the `Write Key` as the username and leave the password blank
6. Copy the generated token and paste it in `src/analytics.ts`(if this file does not exist, run the extension and it should be automatically generated for you) as the value of the `RUDDERSTACK_KEY` variable
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
},
"devcycle-feature-flags.sendMetrics": {
"type": "boolean",
"default": true,
"default": false,
"description": "Allow DevCycle to send usage metrics."
}
}
Expand All @@ -125,11 +125,12 @@
"vscode:prepublish": "yarn run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"pretest": "yarn run compile && yarn run lint",
"pretest": "yarn create-analytics-file && yarn run compile && yarn run lint",
"lint": "eslint src --ext ts",
"test": "node ./out/test/runTest.js",
"publish": "vsce publish",
"package": "vsce package"
"package": "vsce package",
"create-analytics-file": "test -f ./src/analytics.ts || echo 'export const RUDDERSTACK_KEY = \"\";' > ./src/analytics.ts"
},
"devDependencies": {
"@types/chai": "^4.3.5",
Expand Down
43 changes: 43 additions & 0 deletions src/RudderStackService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import axios from "axios";
import * as vscode from 'vscode'
import { RUDDERSTACK_KEY } from "./analytics";
import { getOrganizationId } from "./cli";

type RudderstackEvent = {
event: string,
userId: string,
properties: Record<string, unknown>
}

const rudderstackClient = axios.create({
baseURL: 'https://taplyticsncs.dataplane.rudderstack.com/v1/',
headers: {
Authorization: `Basic ${RUDDERSTACK_KEY}`,
'Content-Type': 'application/json'
}
})

export const trackRudderstackEvent = async (
nsalamad marked this conversation as resolved.
Show resolved Hide resolved
eventName: string
): Promise<void> => {
const sendMetrics = vscode.workspace.getConfiguration('devcycle-feature-flags').get('sendMetrics')
if (sendMetrics) {
const orgId = getOrganizationId()
if (!orgId) { return }
const event = {
event: eventName,
userId: orgId,
properties: {
a0_organization: orgId
}
}
await rudderstackClient.post('track', event).catch((error) => {
if (!axios.isAxiosError(error)) { return }
if (error?.response?.status === 401) {
console.error('Failed to send event. Analytics key is invalid.')
} else {
console.error('Failed to send event. Status: ', error?.response?.status)
}
})
}
}
8 changes: 7 additions & 1 deletion src/StateManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const enum KEYS {
ORGANIZATION = 'organization',
SEND_METRICS_PROMPTED = 'send_metrics_prompted',
CODE_USAGE_KEYS = 'code_usage_keys',
EXTENSION_INSTALLED = 'extension_installed',
}

export class StateManager {
Expand All @@ -18,7 +19,12 @@ export class StateManager {

static clearState() {
this.workspaceState.keys().forEach((key) => {
if (key !== KEYS.ORGANIZATION && key !== KEYS.PROJECT_ID && key !== KEYS.PROJECT_NAME) {
if (
key !== KEYS.PROJECT_ID &&
key !== KEYS.PROJECT_NAME &&
key !== KEYS.ORGANIZATION &&
key !== KEYS.EXTENSION_INSTALLED
) {
this.workspaceState.update(key, undefined)
}
})
Expand Down
8 changes: 4 additions & 4 deletions src/cli/baseCLIController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
await chooseOrganization(organizations)
const org = StateManager.getState(KEYS.ORGANIZATION)
const project = StateManager.getState(KEYS.PROJECT_ID)
if (!org || !project) return
if (!org || !project) { return }

await vscode.commands.executeCommand(
'setContext',
Expand All @@ -108,7 +108,7 @@
ignoreFocusOut: true,
title: 'Select DevCycle Organization',
}))?.value
if (!organization) return
if (!organization) { return }
StateManager.setState(KEYS.ORGANIZATION, organization)
StateManager.setState(KEYS.PROJECT_ID, undefined)

Expand All @@ -134,7 +134,7 @@
ignoreFocusOut: true,
title: 'Select DevCycle Project',
})
if (!project) return
if (!project) { return }
const { code, error } = await execDvc(`projects select --project=${project}`)
if (code === 0) {
await vscode.commands.executeCommand(
Expand Down Expand Up @@ -192,8 +192,8 @@
vscode.workspace.getConfiguration('devcycle-feature-flags').get('cli') ||
'dvc'
const project_id = StateManager.getState(KEYS.PROJECT_ID)
let shellCommand = `${cli} ${cmd} --headless`
let shellCommand = `${cli} ${cmd} --headless --caller vs_code_extension`
if (project_id) shellCommand += ` --project ${project_id}`

Check warning on line 196 in src/cli/baseCLIController.ts

View workflow job for this annotation

GitHub Actions / Test

Expected { after 'if' condition

Check warning on line 196 in src/cli/baseCLIController.ts

View workflow job for this annotation

GitHub Actions / Test

Expected { after 'if' condition
return execShell(shellCommand)
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/SidebarProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class SidebarProvider implements vscode.WebviewViewProvider {

const org = StateManager.getState(KEYS.ORGANIZATION)
const project = StateManager.getState(KEYS.PROJECT_ID)
if (!org || !project) return
if (!org || !project) { return }

await vscode.commands.executeCommand(
'setContext',
Expand Down
48 changes: 40 additions & 8 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { SidebarProvider } from './components/SidebarProvider'

import { UsagesTreeProvider } from './components/UsagesTree'
import { getHoverString } from './components/hoverCard'
import { trackRudderstackEvent } from './RudderStackService'
import { CodeUsageNode } from './components/UsagesTree/CodeUsageNode'

Object.defineProperty(exports, '__esModule', { value: true })
exports.deactivate = exports.activate = void 0
Expand All @@ -29,32 +31,61 @@ export const activate = async (context: vscode.ExtensionContext) => {
StateManager.setState(KEYS.SEND_METRICS_PROMPTED, true)
})
}

if (!StateManager.globalState.get(KEYS.EXTENSION_INSTALLED)) {
await StateManager.globalState.update(KEYS.EXTENSION_INSTALLED, true)
trackRudderstackEvent('Extension Installed')
}

const autoLogin = vscode.workspace
.getConfiguration('devcycle-feature-flags')
.get('loginOnWorkspaceOpen')

const sidebarProvider = new SidebarProvider(context.extensionUri)
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(
'devcycle-sidebar',
sidebarProvider
),
)

const rootPath =
vscode.workspace.workspaceFolders &&
vscode.workspace.workspaceFolders.length > 0
? vscode.workspace.workspaceFolders[0].uri.fsPath
: undefined
const usagesDataProvider = new UsagesTreeProvider(rootPath, context)
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(
'devcycle-sidebar',
sidebarProvider,
),
)
vscode.window.registerTreeDataProvider(
const usagesTreeView = vscode.window.createTreeView(
'devcycleCodeUsages',
usagesDataProvider,
{ treeDataProvider: usagesDataProvider },
)
usagesTreeView.onDidChangeVisibility(async (e) => {
trackRudderstackEvent('Usages Viewed')
})

context.subscriptions.push(
vscode.commands.registerCommand(
'devcycle-featureflags.usagesNodeClicked',
async (node: CodeUsageNode) => {
trackRudderstackEvent('Code Usage Clicked')
}
)
)

usagesTreeView.onDidChangeSelection((e) => {
const node = e.selection[0]
if (node instanceof CodeUsageNode && node.type === 'usage') {
vscode.commands.executeCommand(
'devcycle-featureflags.usagesNodeClicked',
node
)
}
})


context.subscriptions.push(
vscode.commands.registerCommand('devcycle-feature-flags.init', async () => {
trackRudderstackEvent('Init Command Ran')
await init()
}),
)
Expand All @@ -72,6 +103,7 @@ export const activate = async (context: vscode.ExtensionContext) => {
vscode.commands.registerCommand(
'devcycle-feature-flags.logout',
async () => {
trackRudderstackEvent('Logout Command Ran')
await Promise.all([
StateManager.clearState(),
vscode.commands.executeCommand(
Expand Down