diff --git a/src/appDiscovery.ts b/src/appDiscovery.ts index 3ff7532..6938489 100644 --- a/src/appDiscovery.ts +++ b/src/appDiscovery.ts @@ -10,14 +10,20 @@ import type { Parser } from "./core/parser" import { findProjectRoot, uriPath } from "./core/pathUtils" import { buildRouterGraph } from "./core/routerResolver" import { routerNodeToAppDefinition } from "./core/transformer" -import { collectRoutes, countRouters } from "./core/treeUtils" +import { collectRoutes } from "./core/treeUtils" import type { AppDefinition } from "./core/types" import { log } from "./utils/logger" -import { createTimer, trackEntrypointDetected } from "./utils/telemetry" import { vscodeFileSystem } from "./vscode/vscodeFileSystem" export type { EntryPoint } +export interface DiscoveryStats { + detection_method_config: number + detection_method_pyproject: number + detection_method_heuristic: number + folders_with_apps: number +} + /** * Parses an entrypoint string in module:variable notation. * Supports formats like "my_app.main:app" or "main". @@ -137,12 +143,18 @@ async function parsePyprojectForEntryPoint( */ export async function discoverFastAPIApps( parser: Parser, - trackTelemetry = false, -): Promise { +): Promise<{ apps: AppDefinition[]; stats: DiscoveryStats }> { + const stats: DiscoveryStats = { + detection_method_config: 0, + detection_method_pyproject: 0, + detection_method_heuristic: 0, + folders_with_apps: 0, + } + const workspaceFolders = vscode.workspace.workspaceFolders if (!workspaceFolders) { log("No workspace folders found") - return [] + return { apps: [], stats } } log( @@ -152,7 +164,6 @@ export async function discoverFastAPIApps( const apps: AppDefinition[] = [] for (const folder of workspaceFolders) { - const folderTimer = createTimer() let detectionMethod: "config" | "pyproject" | "heuristic" = "heuristic" const folderApps: AppDefinition[] = [] const config = vscode.workspace.getConfiguration("fastapi", folder.uri) @@ -222,23 +233,20 @@ export async function discoverFastAPIApps( } } - const folderRoutes = collectRoutes(folderApps) - if (folderApps.length > 0) { + const folderRoutes = collectRoutes(folderApps) log( `Found ${folderApps.length} FastAPI app(s) with ${folderRoutes.length} route(s) in ${folder.name}`, ) + stats.folders_with_apps++ } - // Track entrypoint detection per workspace folder (initial discovery only) - if (trackTelemetry) { - trackEntrypointDetected({ - duration_ms: folderTimer(), - method: detectionMethod, - success: folderApps.length > 0, - routes_count: folderRoutes.length, - routers_count: countRouters(folderApps), - }) + if (detectionMethod === "config") { + stats.detection_method_config++ + } else if (detectionMethod === "pyproject") { + stats.detection_method_pyproject++ + } else { + stats.detection_method_heuristic++ } } @@ -246,5 +254,5 @@ export async function discoverFastAPIApps( log("No FastAPI apps found in workspace") } - return apps + return { apps, stats } } diff --git a/src/cloud/auth.ts b/src/cloud/auth.ts index d8061ed..053c667 100644 --- a/src/cloud/auth.ts +++ b/src/cloud/auth.ts @@ -74,9 +74,10 @@ export class CloudAuthenticationProvider ) } - startWatching() { + async startWatching() { // Poll for auth changes since we can't use fs.watch in browser // and VS Code's file watcher doesn't work for files outside workspace + this.lastAuthState = await this.hasValidToken() this.pollingInterval = setInterval( () => this.checkAndFireAuthState(), AUTH_POLL_INTERVAL_MS, diff --git a/src/extension.ts b/src/extension.ts index 2f195e0..923c910 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -3,7 +3,7 @@ */ import * as vscode from "vscode" -import { discoverFastAPIApps } from "./appDiscovery" +import { type DiscoveryStats, discoverFastAPIApps } from "./appDiscovery" import { ApiService } from "./cloud/api" import { AUTH_PROVIDER_ID, CloudAuthenticationProvider } from "./cloud/auth" import { LOGS_VIEW_ID, LogsViewProvider } from "./cloud/commands/logs" @@ -70,7 +70,13 @@ export async function activate(context: vscode.ExtensionContext) { // Initialize telemetry await initVSCodeTelemetry(context) - let apps: Awaited> = [] + let apps: AppDefinition[] = [] + let stats: DiscoveryStats = { + detection_method_config: 0, + detection_method_pyproject: 0, + detection_method_heuristic: 0, + folders_with_apps: 0, + } let success = true try { @@ -108,7 +114,9 @@ export async function activate(context: vscode.ExtensionContext) { try { // Discover apps and create providers - apps = await discoverFastAPIApps(parserService, true) + const result = await discoverFastAPIApps(parserService) + apps = result.apps + stats = result.stats } catch (error) { success = false trackActivationFailed(error, "discovery") @@ -128,6 +136,7 @@ export async function activate(context: vscode.ExtensionContext) { routers_count: countRouters(apps), apps_count: apps.length, workspace_folder_count: vscode.workspace.workspaceFolders?.length ?? 0, + ...stats, }) // Create grouping function that groups by workspace folder if there are multiple folders @@ -173,7 +182,7 @@ export async function activate(context: vscode.ExtensionContext) { if (refreshTimeout) clearTimeout(refreshTimeout) refreshTimeout = setTimeout(async () => { if (!parserService) return - const newApps = await discoverFastAPIApps(parserService) + const { apps: newApps } = await discoverFastAPIApps(parserService) if (uri) { await testIndex.invalidateFile(uri.toString()) @@ -236,7 +245,7 @@ export async function activate(context: vscode.ExtensionContext) { // Auth provider must be registered regardless of workspace, // so sign-in works from command palette and Accounts menu in vscode.dev const authProvider = new CloudAuthenticationProvider(context, apiService) - authProvider.startWatching() + await authProvider.startWatching() context.subscriptions.push( { dispose: () => authProvider.dispose() }, @@ -425,7 +434,7 @@ function registerCommands( async () => { if (!parserService) return clearImportCache() - const newApps = await discoverFastAPIApps(parserService) + const { apps: newApps } = await discoverFastAPIApps(parserService) pathOperationProvider.setApps(newApps, groupApps(newApps)) testToRouteProvider.setApps(newApps) }, diff --git a/src/test/cloud/auth.test.ts b/src/test/cloud/auth.test.ts index ca88ebd..48141b9 100644 --- a/src/test/cloud/auth.test.ts +++ b/src/test/cloud/auth.test.ts @@ -568,7 +568,7 @@ suite("cloud/auth", () => { fsStub.fake.readFile.rejects(new Error("File not found")) - provider.startWatching() + await provider.startWatching() await clock.tickAsync(3100) const callCount = fsStub.fake.readFile.callCount diff --git a/src/utils/telemetry/events.ts b/src/utils/telemetry/events.ts index 02862a4..87ecf50 100644 --- a/src/utils/telemetry/events.ts +++ b/src/utils/telemetry/events.ts @@ -1,8 +1,5 @@ import { client } from "./client" -import type { - ActivationEventProps, - EntrypointDetectedEventProps, -} from "./types" +import type { ActivationEventProps } from "./types" export function createTimer(): () => number { const start = performance.now() @@ -13,8 +10,6 @@ export const Events = { ACTIVATED: "extension_activated", ACTIVATION_FAILED: "extension_activation_failed", DEACTIVATED: "extension_deactivated", - ENTRYPOINT_DETECTED: "extension_entrypoint_detected", - CODELENS_PROVIDED: "extension_codelens_provided", CODELENS_CLICKED: "extension_codelens_clicked", TREE_VIEW_VISIBLE: "extension_tree_view_visible", SEARCH_EXECUTED: "extension_search_executed", @@ -125,12 +120,6 @@ export function trackActivationFailed( }) } -export function trackEntrypointDetected( - props: EntrypointDetectedEventProps, -): void { - client.capture(Events.ENTRYPOINT_DETECTED, { ...props }) -} - export function trackTreeViewVisible(): void { client.capture(Events.TREE_VIEW_VISIBLE) } @@ -145,19 +134,6 @@ export function trackSearchExecuted( }) } -export function trackCodeLensProvided( - testCallsCount: number, - matchedCount: number, - type: "test" | "route" = "test", -): void { - client.capture(Events.CODELENS_PROVIDED, { - type, - test_calls_count: testCallsCount, - matched_count: matchedCount, - match_rate: testCallsCount > 0 ? matchedCount / testCallsCount : 0, - }) -} - export function trackDeactivation(): void { const duration = client.getSessionDuration() if (duration !== null) { diff --git a/src/utils/telemetry/index.ts b/src/utils/telemetry/index.ts index 789d2bb..808f226 100644 --- a/src/utils/telemetry/index.ts +++ b/src/utils/telemetry/index.ts @@ -17,16 +17,13 @@ export { trackCloudProjectUnlinked, trackCloudSignIn, trackCloudSignOut, - trackCodeLensProvided, trackDeactivation, - trackEntrypointDetected, trackSearchExecuted, trackTreeViewVisible, } from "./events" export type { ActivationEventProps, ClientInfo, - EntrypointDetectedEventProps, TelemetryConfig, } from "./types" export { diff --git a/src/utils/telemetry/types.ts b/src/utils/telemetry/types.ts index e86ebb9..42c8c78 100644 --- a/src/utils/telemetry/types.ts +++ b/src/utils/telemetry/types.ts @@ -23,12 +23,8 @@ export interface ActivationEventProps { routers_count: number apps_count: number workspace_folder_count: number -} - -export interface EntrypointDetectedEventProps { - duration_ms: number - method: "config" | "pyproject" | "heuristic" - success: boolean - routes_count: number - routers_count: number + detection_method_config: number + detection_method_pyproject: number + detection_method_heuristic: number + folders_with_apps: number } diff --git a/src/vscode/routeToTestCodeLensProvider.ts b/src/vscode/routeToTestCodeLensProvider.ts index e1c064d..75551c9 100644 --- a/src/vscode/routeToTestCodeLensProvider.ts +++ b/src/vscode/routeToTestCodeLensProvider.ts @@ -12,14 +12,12 @@ import { import { type AppDefinition, collectRoutes } from "../core" import type { RouteDefinition } from "../core/types" -import { trackCodeLensProvided } from "../utils/telemetry" import type { TestCallIndex } from "./testIndex" export class RouteToTestCodeLensProvider implements CodeLensProvider { private cachedRoutes: RouteDefinition[] = [] private testIndex: TestCallIndex private indexListener: Disposable - private trackedFiles = new Set() private _onDidChangeCodeLenses = new EventEmitter() readonly onDidChangeCodeLenses = this._onDidChangeCodeLenses.event @@ -34,7 +32,6 @@ export class RouteToTestCodeLensProvider implements CodeLensProvider { setApps(apps: AppDefinition[]): void { this.cachedRoutes = collectRoutes(apps) - this.trackedFiles.clear() this._onDidChangeCodeLenses.fire() } @@ -77,11 +74,6 @@ export class RouteToTestCodeLensProvider implements CodeLensProvider { ) } - if (routes.length > 0 && !this.trackedFiles.has(currentFile)) { - this.trackedFiles.add(currentFile) - trackCodeLensProvided(routes.length, codeLenses.length, "route") - } - return codeLenses } diff --git a/src/vscode/testToRouteCodeLensProvider.ts b/src/vscode/testToRouteCodeLensProvider.ts index 6b59da6..887e8ef 100644 --- a/src/vscode/testToRouteCodeLensProvider.ts +++ b/src/vscode/testToRouteCodeLensProvider.ts @@ -21,7 +21,6 @@ import { } from "../core/pathUtils" import { collectRoutes } from "../core/treeUtils" import type { AppDefinition, SourceLocation } from "../core/types" -import { trackCodeLensProvided } from "../utils/telemetry" export class TestToRouteCodeLensProvider implements CodeLensProvider { private apps: AppDefinition[] = [] @@ -30,8 +29,6 @@ export class TestToRouteCodeLensProvider implements CodeLensProvider { private _onDidChangeCodeLenses = new EventEmitter() readonly onDidChangeCodeLenses = this._onDidChangeCodeLenses.event - private trackedFiles = new Set() - constructor(parser: Parser, apps: AppDefinition[]) { this.parser = parser this.apps = apps @@ -39,7 +36,6 @@ export class TestToRouteCodeLensProvider implements CodeLensProvider { setApps(apps: AppDefinition[]): void { this.apps = apps - this.trackedFiles.clear() this._onDidChangeCodeLenses.fire() } @@ -85,13 +81,6 @@ export class TestToRouteCodeLensProvider implements CodeLensProvider { } } - // Track once per file per session (first open only, edits won't update the count) - const fileKey = document.uri.toString() - if (testClientCalls.length > 0 && !this.trackedFiles.has(fileKey)) { - this.trackedFiles.add(fileKey) - trackCodeLensProvided(testClientCalls.length, codeLenses.length) - } - return codeLenses }