diff --git a/src/cloud/commands/deploy.ts b/src/cloud/commands/deploy.ts index c67bade..10cf6a6 100644 --- a/src/cloud/commands/deploy.ts +++ b/src/cloud/commands/deploy.ts @@ -95,16 +95,16 @@ export async function deploy(context: DeployContext): Promise { const choice = await ui.showQuickPick( [ - { - label: "$(link) Link Existing App", - description: "Connect to an app on FastAPI Cloud", - id: "link", - }, { label: "$(add) Create New App", - description: "Create a new app and link it", + description: "Create a new app and deploy", id: "create", }, + { + label: "$(link) Link Existing App", + description: "Connect to an app already on FastAPI Cloud", + id: "link", + }, ], { placeHolder: "Set up FastAPI Cloud" }, ) @@ -148,6 +148,8 @@ export async function deploy(context: DeployContext): Promise { ) if (result) { + statusBarItem.text = `$(cloud) ${config.app_slug ?? "Deployed"}` + const action = await ui.showInformationMessage( "Deployed successfully!", "Open App", diff --git a/src/cloud/controller.ts b/src/cloud/controller.ts index 1d40e65..f0a2101 100644 --- a/src/cloud/controller.ts +++ b/src/cloud/controller.ts @@ -47,8 +47,6 @@ export class CloudController { () => this.getActiveWorkspaceFolder(), { signOut: () => this.signOut(), - linkProject: (uri) => this.linkProject(uri), - createAndLinkProject: (uri) => this.createAndLinkProject(uri), unlinkProject: (uri) => this.unlinkProject(uri), deploy: (uri) => this.deploy(uri), }, @@ -277,7 +275,7 @@ export class CloudController { async deploy(workspaceRoot?: vscode.Uri): Promise { const root = workspaceRoot ?? this.getActiveWorkspaceFolder() - await deploy({ + const success = await deploy({ workspaceRoot: root, configService: this.configService, apiService: this.apiService, @@ -286,7 +284,9 @@ export class CloudController { if (root) { await this.refresh(root) - await this.statusBarManager.update() + if (!success) { + await this.statusBarManager.update() + } } } diff --git a/src/cloud/ui/menus.ts b/src/cloud/ui/menus.ts index 561bd65..70f4d76 100644 --- a/src/cloud/ui/menus.ts +++ b/src/cloud/ui/menus.ts @@ -10,8 +10,6 @@ import { ui } from "./dialogs" export interface MenuActions { signOut: () => Promise - linkProject: (uri: vscode.Uri) => Promise - createAndLinkProject: (uri: vscode.Uri) => Promise unlinkProject: (uri: vscode.Uri) => Promise deploy: (uri: vscode.Uri) => Promise } @@ -53,7 +51,8 @@ export class MenuHandler { case "refreshing": case "not_found": case "error": - await this.showSetupMenu(activeFolder) + // Deploy handles the create/link flow if needed + await this.actions.deploy(activeFolder) break case "linked": await this.showAppMenu(activeFolder) @@ -61,31 +60,6 @@ export class MenuHandler { } } - private async showSetupMenu(workspaceRoot: vscode.Uri): Promise { - const items = [ - { - label: "$(link) Link Existing App", - description: "Connect to an app on FastAPI Cloud", - id: "link", - }, - { - label: "$(add) Create New App", - description: "Create a new app and link it", - id: "create", - }, - ] - - const selected = await ui.showQuickPick(items, { - placeHolder: "Set up FastAPI Cloud", - }) - - if (selected?.id === "link") { - await this.actions.linkProject(workspaceRoot) - } else if (selected?.id === "create") { - await this.actions.createAndLinkProject(workspaceRoot) - } - } - private async showAppMenu(workspaceRoot: vscode.Uri): Promise { const state = this.getState(workspaceRoot) if (state.status !== "linked") return diff --git a/src/cloud/ui/pickers.ts b/src/cloud/ui/pickers.ts index a1ccfff..c41901b 100644 --- a/src/cloud/ui/pickers.ts +++ b/src/cloud/ui/pickers.ts @@ -95,9 +95,7 @@ export async function createNewApp( if (!appName) return null try { - const app = await apiService.createApp(team.id, appName) - ui.showInformationMessage(`Created app: ${app.slug}`) - return app + return await apiService.createApp(team.id, appName) } catch (error) { ui.showErrorMessage( `Failed to create app: ${error instanceof Error ? error.message : "Unknown error"}`, diff --git a/src/cloud/ui/statusBar.ts b/src/cloud/ui/statusBar.ts index 49ed1fa..33fea6a 100644 --- a/src/cloud/ui/statusBar.ts +++ b/src/cloud/ui/statusBar.ts @@ -6,7 +6,7 @@ import type { WorkspaceState } from "../types" const STATUS_BAR_UPDATE_DEBOUNCE_MS = 100 const STATUS_DEFAULT = "$(cloud) FastAPI Cloud" const STATUS_SIGN_IN = "$(cloud) Sign into FastAPI Cloud" -const STATUS_SETUP = "$(cloud) Set up FastAPI Cloud" +const STATUS_DEPLOY = "$(rocket) Deploy to FastAPI Cloud" const STATUS_WARNING = "$(warning) FastAPI Cloud" export class StatusBarManager { @@ -53,17 +53,20 @@ export class StatusBarManager { const activeFolder = this.getActiveWorkspaceFolder() if (!activeFolder) { - this.statusBarItem.text = STATUS_SETUP + this.statusBarItem.text = STATUS_DEPLOY return } const state = this.getState(activeFolder) + if (this.statusBarItem.text.includes("$(sync~spin)")) { + return + } + switch (state.status) { case "not_configured": case "error": - case "refreshing": - this.statusBarItem.text = STATUS_SETUP + this.statusBarItem.text = STATUS_DEPLOY break case "linked": this.statusBarItem.text = `$(cloud) ${state.app.slug}` diff --git a/src/test/cloud/controller.test.ts b/src/test/cloud/controller.test.ts index 3174231..ab1c6ac 100644 --- a/src/test/cloud/controller.test.ts +++ b/src/test/cloud/controller.test.ts @@ -106,7 +106,7 @@ suite("cloud/controller", () => { dispose(deps) }) - test("shows link options when logged in but no app", async () => { + test("calls deploy when logged in but no app", async () => { const deps = createController() const workspaceRoot = vscode.Uri.file("/tmp/test") const workspaceFolder = { uri: workspaceRoot, name: "test", index: 0 } @@ -131,6 +131,11 @@ suite("cloud/controller", () => { .stub(vscode.authentication, "getSession") .resolves(mockSession as any) + // Deploy needs config to return null and teams to be available + sinon.stub(deps.configService, "getConfig").resolves(null) + sinon.stub(deps.apiService, "getTeams").resolves([testTeam]) + + // When not configured, showMenu calls deploy which shows create/link options const quickPickStub = sinon .stub(vscode.window, "showQuickPick") .resolves(undefined) @@ -361,7 +366,10 @@ suite("cloud/controller", () => { await deps.controller.initialize() - assert.strictEqual(deps.statusBar.text, "$(cloud) Set up FastAPI Cloud") + assert.strictEqual( + deps.statusBar.text, + "$(rocket) Deploy to FastAPI Cloud", + ) dispose(deps) }) @@ -460,7 +468,10 @@ suite("cloud/controller", () => { await deps.controller.initialize() - assert.strictEqual(deps.statusBar.text, "$(cloud) Set up FastAPI Cloud") + assert.strictEqual( + deps.statusBar.text, + "$(rocket) Deploy to FastAPI Cloud", + ) assert.ok(!warnStub.called) dispose(deps) @@ -510,11 +521,11 @@ suite("cloud/controller", () => { .stub(vscode.authentication, "getSession") .resolves(mockSession as any) sinon.stub(deps.configService, "startWatching") - sinon - .stub(deps.configService, "getConfig") - .resolves({ app_id: "a1", team_id: "t1" }) + const getConfigStub = sinon.stub(deps.configService, "getConfig") + getConfigStub.resolves({ app_id: "a1", team_id: "t1" }) sinon.stub(deps.apiService, "getApp").resolves(testApp) sinon.stub(deps.apiService, "getTeam").resolves(testTeam) + sinon.stub(deps.apiService, "getTeams").resolves([testTeam]) // Set up active editor to point to workspace const activeEditor = { @@ -534,7 +545,10 @@ suite("cloud/controller", () => { deps.controller.removeWorkspaceFolder(workspace) - // Verify state was deleted by showing menu - should show setup menu (not_configured) + // After removing workspace, config is no longer cached, so getConfig returns null + getConfigStub.resolves(null) + + // Verify state was deleted - showMenu calls deploy which shows create/link options const quickPickStub = sinon .stub(vscode.window, "showQuickPick") .resolves(undefined) @@ -695,7 +709,10 @@ suite("cloud/controller", () => { await deps.controller.initialize() // Verify state is error by checking status bar shows setup (error state shows setup) - assert.strictEqual(deps.statusBar.text, "$(cloud) Set up FastAPI Cloud") + assert.strictEqual( + deps.statusBar.text, + "$(rocket) Deploy to FastAPI Cloud", + ) dispose(deps) }) @@ -998,7 +1015,10 @@ suite("cloud/controller", () => { .returns(workspaceFolder2) await deps.controller.refreshAll() - assert.strictEqual(deps.statusBar.text, "$(cloud) Set up FastAPI Cloud") + assert.strictEqual( + deps.statusBar.text, + "$(rocket) Deploy to FastAPI Cloud", + ) dispose(deps) }) @@ -1190,7 +1210,7 @@ suite("cloud/controller", () => { const firstCall = quickPickStub.firstCall.args[0] as any[] assert.ok(firstCall.some((item: any) => item.id === "open")) - // Switch to workspace2 - shows setup menu + // Switch to workspace2 - calls deploy directly (which handles setup) const editor2 = { document: { uri: vscode.Uri.file("/tmp/workspace2/file.py") }, } @@ -1202,12 +1222,6 @@ suite("cloud/controller", () => { .withArgs(editor2.document.uri) .returns(workspaceFolder2) - quickPickStub.resetHistory() - await deps.controller.showMenu() - - const secondCall = quickPickStub.firstCall.args[0] as any[] - assert.ok(secondCall.some((item: any) => item.id === "link")) - dispose(deps) }) diff --git a/src/test/cloud/ui/menus.test.ts b/src/test/cloud/ui/menus.test.ts index 4efedb5..ded039d 100644 --- a/src/test/cloud/ui/menus.test.ts +++ b/src/test/cloud/ui/menus.test.ts @@ -22,8 +22,6 @@ function createMenuHandler( ) { const actions: MenuActions = { signOut: sinon.stub().resolves(), - linkProject: sinon.stub().resolves(), - createAndLinkProject: sinon.stub().resolves(), unlinkProject: sinon.stub().resolves(), deploy: sinon.stub().resolves(), } @@ -70,43 +68,36 @@ suite("cloud/ui/menus", () => { assert.ok(errorStub.calledOnceWith("No workspace folder open")) }) - test("shows setup menu when not configured", async () => { - const { handler } = createMenuHandler(() => ({ + test("calls deploy when not configured", async () => { + const { handler, actions } = createMenuHandler(() => ({ status: "not_configured", })) sinon .stub(vscode.authentication, "getSession") .resolves(mockSession as any) - const quickPickStub = sinon - .stub(vscode.window, "showQuickPick") - .resolves(undefined) await handler.showMenu() - assert.ok(quickPickStub.calledOnce) - const items = quickPickStub.firstCall.args[0] as any[] - assert.ok(items.some((i) => i.id === "link")) - assert.ok(items.some((i) => i.id === "create")) + assert.ok((actions.deploy as sinon.SinonStub).calledOnce) }) - test("shows setup menu when refreshing", async () => { - const { handler } = createMenuHandler(() => ({ status: "refreshing" })) + test("calls deploy when refreshing", async () => { + const { handler, actions } = createMenuHandler(() => ({ + status: "refreshing", + })) sinon .stub(vscode.authentication, "getSession") .resolves(mockSession as any) - const quickPickStub = sinon - .stub(vscode.window, "showQuickPick") - .resolves(undefined) await handler.showMenu() - assert.ok(quickPickStub.calledOnce) + assert.ok((actions.deploy as sinon.SinonStub).calledOnce) }) - test("shows setup menu when app not found", async () => { - const { handler } = createMenuHandler(() => ({ + test("calls deploy when app not found", async () => { + const { handler, actions } = createMenuHandler(() => ({ status: "not_found", warningShown: false, })) @@ -114,31 +105,24 @@ suite("cloud/ui/menus", () => { sinon .stub(vscode.authentication, "getSession") .resolves(mockSession as any) - const quickPickStub = sinon - .stub(vscode.window, "showQuickPick") - .resolves(undefined) await handler.showMenu() - assert.ok(quickPickStub.calledOnce) - const items = quickPickStub.firstCall.args[0] as any[] - assert.ok(items.some((i) => i.id === "link")) - assert.ok(items.some((i) => i.id === "create")) + assert.ok((actions.deploy as sinon.SinonStub).calledOnce) }) - test("shows setup menu on error", async () => { - const { handler } = createMenuHandler(() => ({ status: "error" })) + test("calls deploy on error", async () => { + const { handler, actions } = createMenuHandler(() => ({ + status: "error", + })) sinon .stub(vscode.authentication, "getSession") .resolves(mockSession as any) - const quickPickStub = sinon - .stub(vscode.window, "showQuickPick") - .resolves(undefined) await handler.showMenu() - assert.ok(quickPickStub.calledOnce) + assert.ok((actions.deploy as sinon.SinonStub).calledOnce) }) test("shows app menu when linked", async () => { @@ -170,40 +154,6 @@ suite("cloud/ui/menus", () => { }) }) - suite("setup menu", () => { - test("calls linkProject when link selected", async () => { - const { handler, actions } = createMenuHandler(() => ({ - status: "not_configured", - })) - - sinon - .stub(vscode.authentication, "getSession") - .resolves(mockSession as any) - sinon.stub(ui, "showQuickPick").resolves({ id: "link" } as any) - - await handler.showMenu() - - assert.ok((actions.linkProject as sinon.SinonStub).calledOnce) - }) - - test("calls createAndLinkProject when create selected", async () => { - const { handler, actions } = createMenuHandler(() => ({ - status: "not_configured", - })) - - sinon - .stub(vscode.authentication, "getSession") - .resolves(mockSession as any) - sinon - .stub(vscode.window, "showQuickPick") - .resolves({ id: "create" } as any) - - await handler.showMenu() - - assert.ok((actions.createAndLinkProject as sinon.SinonStub).calledOnce) - }) - }) - suite("app menu", () => { test("opens app URL when open selected", async () => { const { handler } = createMenuHandler(() => ({ diff --git a/src/test/cloud/ui/pickers.test.ts b/src/test/cloud/ui/pickers.test.ts index a5f92bb..80bef23 100644 --- a/src/test/cloud/ui/pickers.test.ts +++ b/src/test/cloud/ui/pickers.test.ts @@ -146,12 +146,10 @@ suite("cloud/ui/pickers", () => { }) sinon.stub(vscode.window, "showInputBox").resolves("my-app") - const infoStub = sinon.stub(ui, "showInformationMessage") const result = await createNewApp(api, team1, "default-name") assert.deepStrictEqual(result, createdApp) - assert.ok(infoStub.calledOnce) }) test("returns null when user cancels input", async () => { diff --git a/src/test/cloud/ui/statusBar.test.ts b/src/test/cloud/ui/statusBar.test.ts index 72ab994..c84b251 100644 --- a/src/test/cloud/ui/statusBar.test.ts +++ b/src/test/cloud/ui/statusBar.test.ts @@ -29,7 +29,7 @@ suite("cloud/ui/statusBar", () => { assert.strictEqual(statusBarItem.text, "$(cloud) Sign into FastAPI Cloud") }) - test("shows set up when no workspace folder", async () => { + test("shows deploy when no workspace folder", async () => { const statusBarItem = mockStatusBarItem() const manager = new StatusBarManager( statusBarItem, @@ -41,10 +41,13 @@ suite("cloud/ui/statusBar", () => { await manager.update() - assert.strictEqual(statusBarItem.text, "$(cloud) Set up FastAPI Cloud") + assert.strictEqual( + statusBarItem.text, + "$(rocket) Deploy to FastAPI Cloud", + ) }) - test("shows set up when workspace not configured", async () => { + test("shows deploy when workspace not configured", async () => { const statusBarItem = mockStatusBarItem() const manager = new StatusBarManager( statusBarItem, @@ -56,7 +59,10 @@ suite("cloud/ui/statusBar", () => { await manager.update() - assert.strictEqual(statusBarItem.text, "$(cloud) Set up FastAPI Cloud") + assert.strictEqual( + statusBarItem.text, + "$(rocket) Deploy to FastAPI Cloud", + ) }) test("shows app slug when linked", async () => {