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

Feature: Settings to enable telemetry for extension #55

Merged
merged 20 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
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
}
67 changes: 47 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,46 @@ 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, attributes }: { name: string; attributes?: TraceAttributes }) {
console.log({ name, attributes })
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we doing console log here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because i need to use the attributes and name somehow otherwise it gives unused values errors.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can do it like one of these ways:
start({ _name, _attributes }: { name: string; attributes?: TraceAttributes }) { ... }

start({ }: { name: string; attributes?: TraceAttributes }) { ... }

what do you think @srzainab?

}
end({ attributes }: { attributes?: TraceAttributes }) {
console.log({ attributes })
}
}
class Trace {
tracer: Tracer
provider: BasicTracerProvider
defaultAttributes: Attributes
span: Span | undefined

constructor(context: ExtensionContext) {
this.provider = new BasicTracerProvider({
Expand All @@ -34,43 +68,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
Loading