mirror of
https://github.com/coder/coder.git
synced 2026-11-03 05:55:24 +08:00
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell. Resolves [DEVEX-460](https://linear.app/codercom/issue/DEVEX-460/defer-api-key-generation-in-useapplink-to-on-click-instead-of-page). ## Problem `useAppLink` minted a session key on mount via a `useQuery`: ```ts const { data: apiKeyResponse } = useQuery({ ...apiKey(), enabled: isExternalApp(app) && needsSessionToken(app), }); ``` Whenever any page mounted `useAppLink` for an external app that embeds `$SESSION_TOKEN` in its URL (JetBrains Gateway, Coder Desktop, etc.), it fired `POST /api/v2/users/me/keys` on render, even if the user never clicked the link. Each call minted a fresh session key and produced a `created token` audit-log entry. Simply navigating the dashboard generated a stream of `created token` entries with no real connection activity. This is the follow-up to #22318 (AIGOV-24), which only fixed the built-in VS Code / VS Code Insiders buttons in `WorkspacesTable`. ## Change Mirror the on-click minting pattern from #22318, applied to the shared `useAppLink` hook: - Replace the eager `useQuery(apiKey())` with a `useMutation(() => API.getApiKey())` that runs only when the user clicks a token-bearing external app. `onClick` mints the key, builds the final URL, and navigates via `location.href` (these are always custom-protocol, non-HTTP external apps). - The returned `href` no longer embeds a token; the hook owns opening for token apps. - Replace the `hasToken` field with `isLoading` (mint in-flight) and update call sites so token apps are always clickable, with a loading affordance while a key is being minted. - `TaskApps` external-app menu items now render an anchor with the hook's `onClick` (instead of a `RouterLink` that bypassed it), so the deferred mint runs there too. No API key is minted until the user actually opens an external app. ## Affected call sites - `site/src/modules/apps/useAppLink.ts` (core change) - `site/src/modules/resources/AppLink/AppLink.tsx` - `site/src/pages/AgentsPage/components/WorkspacePill.tsx` - `site/src/pages/TaskPage/TaskApps.tsx` ## Testing Added Storybook interaction tests (`play` functions) in `AppLink.stories.tsx` that assert the fix directly: - `ExternalAppDefersSessionToken`: a token-bearing external app mints **no** key on render, and mints exactly one on click. - `ExternalAppWithoutSessionTokenNeverMints`: an external app without `$SESSION_TOKEN` never mints a key, even on click. Also verified: - `tsc -p .` (frontend typecheck) passes - `biome check` on changed files passes - `vitest` for `AppLink` (incl. new stories), `apps`, `WorkspacePill`, `TaskApps`, and `AppStatuses` stories/tests pass - pre-commit hook (gen/fmt/lint/build) passes ## Manual verification checklist (recommended before merge) - With an external app that uses `$SESSION_TOKEN` (e.g. JetBrains Gateway), open DevTools → Network filtered on `users/me/keys`, reload `/workspaces` and the workspace detail page, and confirm **no** `POST` fires on render. - Confirm a `POST` fires only when the app link is clicked, and the app opens correctly.