Skip to content

Commit

Permalink
Merge pull request #55 from pullflow/feature/disable-telemetry
Browse files Browse the repository at this point in the history
Feature: Settings to enable telemetry for extension
  • Loading branch information
srzainab authored Jan 24, 2024
2 parents 952476c + 2e1f2a2 commit bc06a40
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 31 deletions.
18 changes: 17 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,22 @@
"fontCharacter": "\\e90c"
}
}
},
"configuration": {
"id": "pullflow",
"title": "Pullflow",
"properties": {
"pullflow.telemetry.enabled": {
"type": "boolean",
"default": true,
"markdownDescription": "Enable Pullflow to transmit product usage telemetry. \n\n_**Important:** To activate telemetry transmission, both this setting and the VS Code telemetry option must be enabled. Telemetry will not be sent if either of these settings is disabled._"
},
"pullflow.automaticFlowDetection.enabled": {
"type": "boolean",
"default": true,
"markdownDescription": "Alow Pullflow to automatically detect flow state based on keystrokes.\n\n_**Note:** Extension must be reloaded for this to take affect._"
}
}
}
},
"scripts": {
Expand Down Expand Up @@ -216,4 +232,4 @@
"ts-jest": "^29.1.0",
"uuidv4": "^6.2.13"
}
}
}
8 changes: 4 additions & 4 deletions src/commands/signOut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ export const SignOut = async ({
pollIntervalId: ReturnType<typeof setInterval>
focusStateEvent: ReturnType<typeof window.onDidChangeWindowState>
presenceInterval: {
userFlowIntervalId: ReturnType<typeof setInterval>
textEditorEvent: ReturnType<typeof window.onDidChangeTextEditorSelection>
clearFlowInterval: Function
disposeTextEditorEvent: Function
}
}) => {
await context.secrets.store(AppConfig.app.sessionSecret, '')
await Store.clear(context)
StatusBar.update({ context, statusBar, state: StatusBarState.SignedOut })
clearInterval(pollIntervalId) // stopping polling interval
clearInterval(presenceInterval.userFlowIntervalId) // stopping user flow interval
focusStateEvent.dispose() // removing focus event listener
presenceInterval.textEditorEvent.dispose() // removing text editor event listener
presenceInterval.clearFlowInterval() // stopping user flow interval
presenceInterval.disposeTextEditorEvent() // removing text editor event listener
}
14 changes: 13 additions & 1 deletion src/userPresence/trackUserPresence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ export const trackUserPresence = (
context: ExtensionContext,
statusBar: StatusBarItem
) => {
const flowEnabled = Store.get(context)?.isFlowDetectionEnabled
if (!flowEnabled) {
log.info(`user disabled flow detection`, module)
return {
clearFlowInterval: () => {},
disposeTextEditorEvent: () => {},
}
}

log.info(`started tracking user flow`, module)
const userFlowIntervalId = setInterval(async () => {
await UserPresence.update(context, statusBar)
Expand All @@ -18,7 +27,10 @@ export const trackUserPresence = (
incrementKeyStrokeCount(context)
})

return { userFlowIntervalId, textEditorEvent }
return {
clearFlowInterval: () => clearInterval(userFlowIntervalId),
disposeTextEditorEvent: () => textEditorEvent.dispose(),
}
}

const incrementKeyStrokeCount = (context: ExtensionContext) => {
Expand Down
45 changes: 45 additions & 0 deletions src/utils/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
WindowState,
commands,
window,
workspace,
} from 'vscode'
import { Store } from './store'
import { log } from './logger'
Expand All @@ -27,6 +28,9 @@ export const initialize = async ({
statusBar: StatusBarItem
}) => {
log.info('initializing extension', module)

await initializeConfiguration(context)

const errorCount = { count: 0 }
await PullRequestState.update({
context,
Expand Down Expand Up @@ -116,3 +120,44 @@ const setSpaceUsers = async ({
spaceUsers: spaceUsers.spaceUsers,
})
}

const extensionTelemetryFlag = () =>
getPullflowConfig('telemetry.enabled', true)

const vscodeTelemetryFlag = () =>
workspace.getConfiguration('telemetry').get<boolean>('enableTelemetry')

const initializeConfiguration = async (context: ExtensionContext) => {
await Store.set(context, {
isTelemetryEnabled: vscodeTelemetryFlag() && extensionTelemetryFlag(),
isFlowDetectionEnabled: !!getPullflowConfig(
'automaticFlowDetection.enabled'
),
})

const disposable = workspace.onDidChangeConfiguration(async (event) => {
if (
event.affectsConfiguration('telemetry.enableTelemetry') ||
event.affectsConfiguration('pullflow.telemetry.enabled')
) {
await Store.set(context, {
isTelemetryEnabled: vscodeTelemetryFlag() && extensionTelemetryFlag(),
})
}

if (event.affectsConfiguration('pullflow.automaticFlowDetection.enabled')) {
await Store.set(context, {
isFlowDetectionEnabled: !!getPullflowConfig(
'automaticFlowDetection.enabled'
),
})
}
})
context.subscriptions.push(disposable)
}

const getPullflowConfig = (key: string, defaultValue?: boolean) => {
const config = workspace.getConfiguration('pullflow')
const value = config.get<boolean>(key)
return value ?? defaultValue
}
63 changes: 43 additions & 20 deletions src/utils/trace.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-unused-vars */
import { Span, Tracer, SpanKind, Attributes } from '@opentelemetry/api'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { Resource } from '@opentelemetry/resources'
Expand All @@ -10,13 +11,42 @@ import { ExtensionContext, extensions } from 'vscode'
import { TraceAttributes } from './types'
import { Store } from './store'
import { AppConfig } from './appConfig'
import { log } from './logger'

const extensionInfo = extensions.getExtension('Pullflow.pullflow')?.packageJSON

export class Trace {
type FakeTracer = {}
type FakeBasicTracerProvider = {}
type FakeAttribute = {}

export function instantiatePullflowTracer(context: ExtensionContext) {
const { isTelemetryEnabled } = Store.get(context)
if (isTelemetryEnabled) {
return new Trace(context)
} else {
return new FakeTrace(context)
}
}
class FakeTrace {
tracer: FakeTracer
provider: FakeBasicTracerProvider
defaultAttributes: FakeAttribute

constructor(_context: ExtensionContext) {
this.provider = {}
this.tracer = {}
this.defaultAttributes = {}
}

dispose(): void {}
start({}: { name: string; attributes?: TraceAttributes }) {}
end({}: { attributes?: TraceAttributes }) {}
}
class Trace {
tracer: Tracer
provider: BasicTracerProvider
defaultAttributes: Attributes
span: Span | undefined

constructor(context: ExtensionContext) {
this.provider = new BasicTracerProvider({
Expand All @@ -34,43 +64,36 @@ export class Trace {
this.tracer = this.provider.getTracer(extensionInfo.name)
const { user } = Store.get(context)
this.defaultAttributes = user || {}
this.span = undefined
}

dispose(): void {
void this.provider.shutdown()
}

start({
name,
attributes,
}: {
name: string
attributes?: TraceAttributes
}): Span {
const span = this.tracer.startSpan(name, {
start({ name, attributes }: { name: string; attributes?: TraceAttributes }) {
this.span = this.tracer.startSpan(name, {
kind: SpanKind.INTERNAL,
startTime: Date.now(),
})
if (attributes)
span.setAttributes({
this.span.setAttributes({
...attributes,
...this.defaultAttributes,
})
return span
}

end({
span,
attributes,
}: {
span: Span
attributes?: TraceAttributes
}): void {
end({ attributes }: { attributes?: TraceAttributes }): void {
if (this.span === undefined) {
log.warn('span is undefined', 'trace.ts')
return
}
if (attributes)
span.setAttributes({
this.span.setAttributes({
...attributes,
...this.defaultAttributes,
})
span.end(Date.now())
this.span.end(Date.now())
this.span = undefined
}
}
2 changes: 2 additions & 0 deletions src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ export type CacheObject = {
keyStrokeCount?: number
lastKeyStrokeTime?: number | null
previousPresenceStatus?: PresenceStatus
isTelemetryEnabled?: boolean
isFlowDetectionEnabled?: boolean
}
export enum StatusBarState {
Loading,
Expand Down
8 changes: 3 additions & 5 deletions src/views/quickpicks/quickPick.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ExtensionContext, QuickPickItem, window } from 'vscode'
import { Trace } from '../../utils/trace'
import { instantiatePullflowTracer } from '../../utils/trace'

export const QuickPick = {
create: <Type extends QuickPickItem>({
Expand All @@ -17,8 +17,8 @@ export const QuickPick = {
}) => {
const quickPick = window.createQuickPick<Type>()

const trace = new Trace(context)
const span = trace.start({
const trace = instantiatePullflowTracer(context)
trace.start({
name: title,
})

Expand All @@ -29,7 +29,6 @@ export const QuickPick = {

quickPick.onDidHide(() => {
trace.end({
span,
attributes: {
title,
},
Expand All @@ -39,7 +38,6 @@ export const QuickPick = {

quickPick.onDidAccept(() => {
trace.end({
span,
attributes: {
title,
selectedItem: quickPick.selectedItems[0]?.label,
Expand Down

0 comments on commit bc06a40

Please sign in to comment.