From 3cefad223d49376d9350e0c0617833a2a538a50c Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Thu, 25 Jan 2024 16:35:07 +0100 Subject: [PATCH 1/9] Enable running queries/mutations in production --- firebase-vscode/package.json | 7 ++- .../src/firemat/code-lens-provider.ts | 21 +++++--- firebase-vscode/src/firemat/execution.ts | 51 +++++++++++++++---- 3 files changed, 63 insertions(+), 16 deletions(-) diff --git a/firebase-vscode/package.json b/firebase-vscode/package.json index 3dccbb2cb72..d1321390675 100644 --- a/firebase-vscode/package.json +++ b/firebase-vscode/package.json @@ -53,6 +53,11 @@ "type": "boolean", "default": true, "description": "Enable web frameworks" + }, + "firebase.firemat.alwaysAllowMutationsInProduction": { + "type": "boolean", + "default": false, + "description": "Always allow mutations in production. If false (default), trying to run a mutation in production will open a confirmation modal." } } }, @@ -198,4 +203,4 @@ "webpack-cli": "^5.0.1", "webpack-merge": "^5.8.0" } -} \ No newline at end of file +} diff --git a/firebase-vscode/src/firemat/code-lens-provider.ts b/firebase-vscode/src/firemat/code-lens-provider.ts index ca6bb31aef4..508b537290e 100644 --- a/firebase-vscode/src/firemat/code-lens-provider.ts +++ b/firebase-vscode/src/firemat/code-lens-provider.ts @@ -60,7 +60,7 @@ export class OperationCodeLensProvider extends ComputedCodeLensProvider { ): vscode.CodeLens[] { // Wait for configs to be loaded and emulator to be running const configs = this.watch(firematConfig); - if (!configs || !this.watch(isFirematEmulatorRunning)) { + if (!configs) { return []; } @@ -82,12 +82,22 @@ export class OperationCodeLensProvider extends ComputedCodeLensProvider { position: position, }; const opKind = x.operation as string; // query or mutation - const schemaPath = configs.schema.main.source; - if (isPathInside(document.fileName, schemaPath)) { + const isInSchemaFolder = isPathInside( + document.fileName, + configs.schema.main.source, + ); + const isInOperationFolder = isPathInside( + document.fileName, + configs.operationSet.crud.source, + ); + + if (isInSchemaFolder || isInOperationFolder) { codeLenses.push( new vscode.CodeLens(range, { - title: `$(play) Execute ${opKind}`, + title: isFirematEmulatorRunning.value + ? `$(play) Run` + : `$(play) Run (production)`, command: "firebase.firemat.executeOperation", tooltip: "Execute the operation (⌘+enter or Ctrl+Enter)", arguments: [x, operationLocation], @@ -95,8 +105,7 @@ export class OperationCodeLensProvider extends ComputedCodeLensProvider { ); } - const connectorPath = configs.operationSet.crud.source; - if (!isPathInside(document.fileName, connectorPath)) { + if (this.watch(isFirematEmulatorRunning) && !isInOperationFolder) { codeLenses.push( new vscode.CodeLens(range, { title: `$(plug) Move to connector`, diff --git a/firebase-vscode/src/firemat/execution.ts b/firebase-vscode/src/firemat/execution.ts index 0cf8cf9e2e4..d59d180c1e4 100644 --- a/firebase-vscode/src/firemat/execution.ts +++ b/firebase-vscode/src/firemat/execution.ts @@ -1,4 +1,8 @@ -import vscode, { Disposable, ExtensionContext } from "vscode"; +import vscode, { + ConfigurationTarget, + Disposable, + ExtensionContext, +} from "vscode"; import { ExtensionBrokerImpl } from "../extension-broker"; import { registerWebview } from "../webview"; import { ExecutionHistoryTreeDataProvider } from "./execution-history-provider"; @@ -13,29 +17,30 @@ import { updateExecution, } from "./execution-store"; import { batch, effect } from "@preact/signals-core"; -import { OperationDefinitionNode, print } from "graphql"; +import { OperationDefinitionNode, OperationTypeNode, print } from "graphql"; import { FirematService } from "./service"; import { FirematError, toSerializedError } from "../../common/error"; import { OperationLocation } from "./types"; +import { isFirematEmulatorRunning } from "../core/emulators"; export function registerExecution( context: ExtensionContext, broker: ExtensionBrokerImpl, - firematService: FirematService + firematService: FirematService, ): Disposable { const treeDataProvider = new ExecutionHistoryTreeDataProvider(); const executionHistoryTreeView = vscode.window.createTreeView( "firemat-execution-history", { treeDataProvider, - } + }, ); // Select the corresponding tree-item when the selected-execution-id updates effect(() => { const id = selectedExecutionId.value; const selectedItem = treeDataProvider.executionItems.find( - ({ item }) => item.executionId === id + ({ item }) => item.executionId === id, ); executionHistoryTreeView.reveal(selectedItem, { select: true }); }); @@ -58,8 +63,36 @@ export function registerExecution( async function executeOperation( ast: OperationDefinitionNode, - { document, documentPath, position }: OperationLocation + { document, documentPath, position }: OperationLocation, ) { + const configs = vscode.workspace.getConfiguration("firebase.firemat"); + const alwaysSettingsKey = "alwaysAllowMutationsInProduction"; + + // Warn against using mutations in production. + if ( + !isFirematEmulatorRunning.value && + !configs.get(alwaysSettingsKey) && + ast.operation === OperationTypeNode.MUTATION + ) { + const always = "Yes (always)"; + const yes = "Yes"; + const result = await vscode.window.showWarningMessage( + "You are about to perform a mutation in production environment. Are you sure?", + { modal: true }, + yes, + always, + ); + + if (result !== always && result !== yes) { + return; + } + + // If the user selects "always", we update User settings. + if (result === always) { + configs.update(alwaysSettingsKey, true, ConfigurationTarget.Global); + } + } + const item = createExecution({ label: ast.name?.value ?? "anonymous", timestamp: Date.now(), @@ -132,13 +165,13 @@ export function registerExecution( executionHistoryTreeView, vscode.commands.registerCommand( "firebase.firemat.executeOperation", - executeOperation + executeOperation, ), vscode.commands.registerCommand( "firebase.firemat.selectExecutionResultToShow", (executionId) => { selectExecutionId(executionId); - } - ) + }, + ), ); } From f1645f53e01218805b7a4b63a8405f8912b1e9bd Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Fri, 26 Jan 2024 17:49:36 +0100 Subject: [PATCH 2/9] Add Firebase Data Connect view --- firebase-vscode/package.json | 7 ++++- firebase-vscode/src/firemat/index.ts | 2 ++ firebase-vscode/webviews/firemat.entry.tsx | 30 +++++++++++++++++++++ firebase-vscode/webviews/globals/ux-text.ts | 4 +++ 4 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 firebase-vscode/webviews/firemat.entry.tsx diff --git a/firebase-vscode/package.json b/firebase-vscode/package.json index d1321390675..acfc37f89ae 100644 --- a/firebase-vscode/package.json +++ b/firebase-vscode/package.json @@ -101,9 +101,14 @@ "id": "sidebar", "name": "Firebase" }, + { + "type": "webview", + "id": "firemat", + "name": "Firebase Data Connect" + }, { "id": "firebase.firemat.explorerView", - "name": "Firemat Explorer" + "name": "FDC Explorer" } ], "firebase-firemat-execution-view": [ diff --git a/firebase-vscode/src/firemat/index.ts b/firebase-vscode/src/firemat/index.ts index cae64576a97..49a6c084457 100644 --- a/firebase-vscode/src/firemat/index.ts +++ b/firebase-vscode/src/firemat/index.ts @@ -12,6 +12,7 @@ import { } from "./code-lens-provider"; import { registerConnectors } from "./connectors"; import { AuthService } from "../auth/service"; +import { registerWebview } from "../webview"; // import { setupLanguageClient } from "./language-client"; const firematEndpoint = signal(undefined); @@ -42,6 +43,7 @@ export function registerFiremat( return Disposable.from( registerExecution(context, broker, firematService), registerExplorer(context, broker, firematService), + registerWebview({ name: "firemat", context, broker }), registerAdHoc(context, broker), registerConnectors(context, broker, firematService), operationCodeLensProvider, diff --git a/firebase-vscode/webviews/firemat.entry.tsx b/firebase-vscode/webviews/firemat.entry.tsx new file mode 100644 index 00000000000..adc045decb8 --- /dev/null +++ b/firebase-vscode/webviews/firemat.entry.tsx @@ -0,0 +1,30 @@ +import React, { useEffect, useState } from "react"; +import { createRoot } from "react-dom/client"; +import { + VSCodeButton, + VSCodeDropdown, + VSCodeOption, + VSCodeTextArea, +} from "@vscode/webview-ui-toolkit/react"; +import { Spacer } from "./components/ui/Spacer"; +import { broker } from "./globals/html-broker"; +import styles from "./globals/index.scss"; +import { UserMockKind } from "../common/messaging/protocol"; +import { TEXT } from "./globals/ux-text"; + +// Prevent webpack from removing the `style` import above +styles; + +const root = createRoot(document.getElementById("root")!); +root.render(); + +function Firemat() { + return ( + <> + + {TEXT.DEPLOY_FIREMAT} + + {TEXT.CONNECT_TO_INSTANCE} + + ); +} diff --git a/firebase-vscode/webviews/globals/ux-text.ts b/firebase-vscode/webviews/globals/ux-text.ts index c1f7a67eccb..1068d28ba65 100644 --- a/firebase-vscode/webviews/globals/ux-text.ts +++ b/firebase-vscode/webviews/globals/ux-text.ts @@ -32,4 +32,8 @@ export const TEXT = { DEPLOYING_IN_PROGRESS: "Deploying...", DEPLOYING_PROGRESS_FRAMEWORK: "Deploying... this may take a few minutes.", + + DEPLOY_FIREMAT: "Deploy to production", + + CONNECT_TO_INSTANCE: "Connect to instance", }; From 5aa7d8d5eb888ec71cbd2daa1a503955d9d95346 Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Fri, 26 Jan 2024 18:10:31 +0100 Subject: [PATCH 3/9] Add Instance Picker --- firebase-vscode/common/messaging/protocol.ts | 14 +++-- firebase-vscode/package.json | 2 +- .../src/firemat/firebase-data-connect.ts | 55 +++++++++++++++++++ firebase-vscode/src/firemat/index.ts | 3 +- firebase-vscode/webviews/firemat.entry.tsx | 16 ++---- 5 files changed, 73 insertions(+), 17 deletions(-) create mode 100644 firebase-vscode/src/firemat/firebase-data-connect.ts diff --git a/firebase-vscode/common/messaging/protocol.ts b/firebase-vscode/common/messaging/protocol.ts index 9fcacf341b5..9caa218f38c 100644 --- a/firebase-vscode/common/messaging/protocol.ts +++ b/firebase-vscode/common/messaging/protocol.ts @@ -23,11 +23,12 @@ export type UserMock = claims: string; }; -type DeepReadOnly = T extends Record - ? { readonly [K in keyof T]: DeepReadOnly } - : T extends Array - ? ReadonlyArray> - : T; +type DeepReadOnly = + T extends Record + ? { readonly [K in keyof T]: DeepReadOnly } + : T extends Array + ? ReadonlyArray> + : T; /** The `firemat.yaml` content */ export type FirematConfig = DeepReadOnly<{ @@ -119,6 +120,9 @@ export interface WebviewToExtensionParamsMap { chooseQuickstartDir: {}; notifyAuthUserMockChange: UserMock; + + /** Opens the "connect to instance" picker */ + connectToInstance: void; } export interface FirematResults { diff --git a/firebase-vscode/package.json b/firebase-vscode/package.json index acfc37f89ae..504a2548a48 100644 --- a/firebase-vscode/package.json +++ b/firebase-vscode/package.json @@ -208,4 +208,4 @@ "webpack-cli": "^5.0.1", "webpack-merge": "^5.8.0" } -} +} \ No newline at end of file diff --git a/firebase-vscode/src/firemat/firebase-data-connect.ts b/firebase-vscode/src/firemat/firebase-data-connect.ts new file mode 100644 index 00000000000..6f4fea4a028 --- /dev/null +++ b/firebase-vscode/src/firemat/firebase-data-connect.ts @@ -0,0 +1,55 @@ +import * as vscode from "vscode"; +import { registerWebview } from "../webview"; +import { ExtensionBrokerImpl } from "../extension-broker"; +import { isFirematEmulatorRunning } from "../core/emulators"; +import { computed, effect, signal } from "@preact/signals-core"; + +const selectedInstance = signal(undefined); + +export function registerFirebaseDataConnectView( + context: vscode.ExtensionContext, + broker: ExtensionBrokerImpl, +): vscode.Disposable { + const instanceOptions = computed(() => { + // Some fake options + const options = ["asia-east1", "europe-north1", "wonderland2"]; + + // We start with the emulator option + const emulator = "Emulator"; + + // TODO refactor "start emulator" logic to enable the picker to start emulators + if (isFirematEmulatorRunning.value) { + options.splice(0, 0, emulator); + } + + return options; + }); + + return vscode.Disposable.from( + { + // Handle cases where the emulator is the currently selected instance, + // and the emulator is stopped. + dispose: effect(() => { + const isSelectedInstanceInOptions = instanceOptions.value?.includes( + selectedInstance.value, + ); + + if (!isSelectedInstanceInOptions) { + selectedInstance.value = undefined; + } + }), + }, + { + dispose: broker.on("connectToInstance", async () => { + const selected = await vscode.window.showQuickPick( + instanceOptions.value, + ); + if (!selected) { + return; + } + }), + }, + + registerWebview({ name: "firemat", context, broker }), + ); +} diff --git a/firebase-vscode/src/firemat/index.ts b/firebase-vscode/src/firemat/index.ts index 49a6c084457..dcc5c728276 100644 --- a/firebase-vscode/src/firemat/index.ts +++ b/firebase-vscode/src/firemat/index.ts @@ -13,6 +13,7 @@ import { import { registerConnectors } from "./connectors"; import { AuthService } from "../auth/service"; import { registerWebview } from "../webview"; +import { registerFirebaseDataConnectView } from "./firebase-data-connect"; // import { setupLanguageClient } from "./language-client"; const firematEndpoint = signal(undefined); @@ -43,7 +44,7 @@ export function registerFiremat( return Disposable.from( registerExecution(context, broker, firematService), registerExplorer(context, broker, firematService), - registerWebview({ name: "firemat", context, broker }), + registerFirebaseDataConnectView(context, broker), registerAdHoc(context, broker), registerConnectors(context, broker, firematService), operationCodeLensProvider, diff --git a/firebase-vscode/webviews/firemat.entry.tsx b/firebase-vscode/webviews/firemat.entry.tsx index adc045decb8..5790ee02d47 100644 --- a/firebase-vscode/webviews/firemat.entry.tsx +++ b/firebase-vscode/webviews/firemat.entry.tsx @@ -1,16 +1,10 @@ -import React, { useEffect, useState } from "react"; +import React from "react"; import { createRoot } from "react-dom/client"; -import { - VSCodeButton, - VSCodeDropdown, - VSCodeOption, - VSCodeTextArea, -} from "@vscode/webview-ui-toolkit/react"; +import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"; import { Spacer } from "./components/ui/Spacer"; -import { broker } from "./globals/html-broker"; import styles from "./globals/index.scss"; -import { UserMockKind } from "../common/messaging/protocol"; import { TEXT } from "./globals/ux-text"; +import { broker } from "./globals/html-broker"; // Prevent webpack from removing the `style` import above styles; @@ -24,7 +18,9 @@ function Firemat() { {TEXT.DEPLOY_FIREMAT} - {TEXT.CONNECT_TO_INSTANCE} + broker.send("connectToInstance")}> + {TEXT.CONNECT_TO_INSTANCE} + ); } From 298585cbf3f14724f25d4302d3798211dfc6359e Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Fri, 26 Jan 2024 18:23:08 +0100 Subject: [PATCH 4/9] Update codelense logic to use instance picker --- firebase-vscode/src/firemat/code-lens-provider.ts | 10 +++++----- firebase-vscode/src/firemat/execution.ts | 4 ++-- firebase-vscode/src/firemat/firebase-data-connect.ts | 9 ++++++--- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/firebase-vscode/src/firemat/code-lens-provider.ts b/firebase-vscode/src/firemat/code-lens-provider.ts index 508b537290e..b07c75a0291 100644 --- a/firebase-vscode/src/firemat/code-lens-provider.ts +++ b/firebase-vscode/src/firemat/code-lens-provider.ts @@ -4,9 +4,10 @@ import { OperationLocation } from "./types"; import { Disposable } from "vscode"; import { isFirematEmulatorRunning } from "../core/emulators"; -import { Signal, computed } from "@preact/signals-core"; +import { Signal } from "@preact/signals-core"; import { firematConfig } from "../core/config"; import path from "path"; +import { selectedInstance } from "./firebase-data-connect"; abstract class ComputedCodeLensProvider implements vscode.CodeLensProvider { private readonly _onChangeCodeLensesEmitter = new vscode.EventEmitter(); @@ -91,13 +92,12 @@ export class OperationCodeLensProvider extends ComputedCodeLensProvider { document.fileName, configs.operationSet.crud.source, ); + const instance = this.watch(selectedInstance); - if (isInSchemaFolder || isInOperationFolder) { + if (instance && (isInSchemaFolder || isInOperationFolder)) { codeLenses.push( new vscode.CodeLens(range, { - title: isFirematEmulatorRunning.value - ? `$(play) Run` - : `$(play) Run (production)`, + title: `$(play) Run (${instance})`, command: "firebase.firemat.executeOperation", tooltip: "Execute the operation (⌘+enter or Ctrl+Enter)", arguments: [x, operationLocation], diff --git a/firebase-vscode/src/firemat/execution.ts b/firebase-vscode/src/firemat/execution.ts index d59d180c1e4..f884129d5dd 100644 --- a/firebase-vscode/src/firemat/execution.ts +++ b/firebase-vscode/src/firemat/execution.ts @@ -21,7 +21,7 @@ import { OperationDefinitionNode, OperationTypeNode, print } from "graphql"; import { FirematService } from "./service"; import { FirematError, toSerializedError } from "../../common/error"; import { OperationLocation } from "./types"; -import { isFirematEmulatorRunning } from "../core/emulators"; +import { selectedInstance } from "./firebase-data-connect"; export function registerExecution( context: ExtensionContext, @@ -70,7 +70,7 @@ export function registerExecution( // Warn against using mutations in production. if ( - !isFirematEmulatorRunning.value && + selectedInstance.value !== "emulator" && !configs.get(alwaysSettingsKey) && ast.operation === OperationTypeNode.MUTATION ) { diff --git a/firebase-vscode/src/firemat/firebase-data-connect.ts b/firebase-vscode/src/firemat/firebase-data-connect.ts index 6f4fea4a028..508d3015f96 100644 --- a/firebase-vscode/src/firemat/firebase-data-connect.ts +++ b/firebase-vscode/src/firemat/firebase-data-connect.ts @@ -4,7 +4,7 @@ import { ExtensionBrokerImpl } from "../extension-broker"; import { isFirematEmulatorRunning } from "../core/emulators"; import { computed, effect, signal } from "@preact/signals-core"; -const selectedInstance = signal(undefined); +export const selectedInstance = signal(undefined); export function registerFirebaseDataConnectView( context: vscode.ExtensionContext, @@ -15,7 +15,7 @@ export function registerFirebaseDataConnectView( const options = ["asia-east1", "europe-north1", "wonderland2"]; // We start with the emulator option - const emulator = "Emulator"; + const emulator = "emulator"; // TODO refactor "start emulator" logic to enable the picker to start emulators if (isFirematEmulatorRunning.value) { @@ -29,13 +29,14 @@ export function registerFirebaseDataConnectView( { // Handle cases where the emulator is the currently selected instance, // and the emulator is stopped. + // This also initializes the selectedInstance value to the first instance. dispose: effect(() => { const isSelectedInstanceInOptions = instanceOptions.value?.includes( selectedInstance.value, ); if (!isSelectedInstanceInOptions) { - selectedInstance.value = undefined; + selectedInstance.value = instanceOptions.value?.[0]; } }), }, @@ -47,6 +48,8 @@ export function registerFirebaseDataConnectView( if (!selected) { return; } + + selectedInstance.value = selected; }), }, From e849ad189c1c5f6cbc2bdc06b71e39dbb9db374e Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Fri, 26 Jan 2024 18:37:20 +0100 Subject: [PATCH 5/9] Add instance in status bar --- .../src/firemat/firebase-data-connect.ts | 37 +++++++++++++++---- firebase-vscode/src/firemat/index.ts | 1 - 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/firebase-vscode/src/firemat/firebase-data-connect.ts b/firebase-vscode/src/firemat/firebase-data-connect.ts index 508d3015f96..ed17ad83491 100644 --- a/firebase-vscode/src/firemat/firebase-data-connect.ts +++ b/firebase-vscode/src/firemat/firebase-data-connect.ts @@ -25,7 +25,35 @@ export function registerFirebaseDataConnectView( return options; }); + const selectedInstanceStatus = vscode.window.createStatusBarItem( + vscode.StatusBarAlignment.Left, + ); + selectedInstanceStatus.tooltip = "Select a Firebase instance"; + selectedInstanceStatus.command = "firebase.firemat.connectToInstance"; + return vscode.Disposable.from( + vscode.commands.registerCommand( + "firebase.firemat.connectToInstance", + async () => { + const selected = await vscode.window.showQuickPick( + instanceOptions.value, + ); + if (!selected) { + return; + } + + selectedInstance.value = selected; + }, + ), + + selectedInstanceStatus, + { + // Sync the status bar with the selected instance + dispose: effect(() => { + selectedInstanceStatus.text = selectedInstance.value ?? ""; + selectedInstanceStatus.show(); + }), + }, { // Handle cases where the emulator is the currently selected instance, // and the emulator is stopped. @@ -42,14 +70,7 @@ export function registerFirebaseDataConnectView( }, { dispose: broker.on("connectToInstance", async () => { - const selected = await vscode.window.showQuickPick( - instanceOptions.value, - ); - if (!selected) { - return; - } - - selectedInstance.value = selected; + vscode.commands.executeCommand("firebase.firemat.connectToInstance"); }), }, diff --git a/firebase-vscode/src/firemat/index.ts b/firebase-vscode/src/firemat/index.ts index dcc5c728276..fb8f5b1a442 100644 --- a/firebase-vscode/src/firemat/index.ts +++ b/firebase-vscode/src/firemat/index.ts @@ -12,7 +12,6 @@ import { } from "./code-lens-provider"; import { registerConnectors } from "./connectors"; import { AuthService } from "../auth/service"; -import { registerWebview } from "../webview"; import { registerFirebaseDataConnectView } from "./firebase-data-connect"; // import { setupLanguageClient } from "./language-client"; From 461be83ff7fe521ab31709cf825b30b681af2e2c Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Fri, 26 Jan 2024 18:50:35 +0100 Subject: [PATCH 6/9] Add project in statusbar --- firebase-vscode/src/core/project.ts | 110 ++++++++++++++++----------- firebase-vscode/src/firemat/index.ts | 16 +++- 2 files changed, 81 insertions(+), 45 deletions(-) diff --git a/firebase-vscode/src/core/project.ts b/firebase-vscode/src/core/project.ts index 77756d8b0fa..8b576d2f05f 100644 --- a/firebase-vscode/src/core/project.ts +++ b/firebase-vscode/src/core/project.ts @@ -12,7 +12,9 @@ import { updateFirebaseRCProject } from "../config-files"; import { globalSignal } from "../utils/globals"; /** Available projects */ -export const projects = globalSignal>({}); +export const projects = globalSignal>( + {}, +); /** Currently selected project ID */ export const currentProjectId = globalSignal(""); @@ -46,7 +48,7 @@ export function registerProject({ context: ExtensionContext; broker: ExtensionBrokerImpl; }): Disposable { - effect(async () => { + const effect1 = effect(async () => { const user = currentUser.value; if (user) { pluginLogger.info("(Core:Project) New user detected, fetching projects"); @@ -58,68 +60,88 @@ export function registerProject({ } }); - effect(() => { + const effect2 = effect(() => { broker.send("notifyProjectChanged", { projectId: currentProject.value?.projectId ?? "", }); }); // Update .firebaserc with defined project ID - effect(() => { + const effect3 = effect(() => { const projectId = currentProjectId.value; if (projectId) { updateFirebaseRCProject(context, "default", currentProjectId.value); } }); - broker.on("getInitialData", () => { + // Initialize currentProjectId to default project ID + const effect4 = effect(() => { + if (!currentProjectId.value) { + currentProjectId.value = firebaseRC.value?.projects.default; + } + }); + + const onGetInitialData = broker.on("getInitialData", () => { broker.send("notifyProjectChanged", { projectId: currentProject.value?.projectId ?? "", }); }); - broker.on("selectProject", async () => { - if (process.env.MONOSPACE_ENV) { - pluginLogger.debug( - "selectProject: found MONOSPACE_ENV, " + - "prompting user using external flow", - ); - /** - * Monospace case: use Monospace flow - */ - const monospaceExtension = - vscode.extensions.getExtension("google.monospace"); - process.env.MONOSPACE_DAEMON_PORT = - monospaceExtension.exports.getMonospaceDaemonPort(); - try { - const projectId = await selectProjectInMonospace({ - projectRoot: currentOptions.value.cwd, - project: undefined, - isVSCE: true, - }); - - if (projectId) { - currentProjectId.value = projectId; - } - } catch (e) { - pluginLogger.error(e); - } - } else if (isServiceAccount.value) { - return; - } else { - try { - currentProjectId.value = await promptUserForProject( - userScopedProjects.value, + const selectProjectCommand = vscode.commands.registerCommand( + "firebase.selectProject", + async () => { + if (process.env.MONOSPACE_ENV) { + pluginLogger.debug( + "selectProject: found MONOSPACE_ENV, " + + "prompting user using external flow", ); - } catch (e) { - vscode.window.showErrorMessage(e.message); + /** + * Monospace case: use Monospace flow + */ + const monospaceExtension = + vscode.extensions.getExtension("google.monospace"); + process.env.MONOSPACE_DAEMON_PORT = + monospaceExtension.exports.getMonospaceDaemonPort(); + try { + const projectId = await selectProjectInMonospace({ + projectRoot: currentOptions.value.cwd, + project: undefined, + isVSCE: true, + }); + + if (projectId) { + currentProjectId.value = projectId; + } + } catch (e) { + pluginLogger.error(e); + } + } else if (isServiceAccount.value) { + return; + } else { + try { + currentProjectId.value = await promptUserForProject( + userScopedProjects.value, + ); + } catch (e) { + vscode.window.showErrorMessage(e.message); + } } - } - }); + }, + ); + + const onSelectProject = broker.on("selectProject", () => + vscode.commands.executeCommand("firebase.selectProject"), + ); - return { - dispose() {}, - }; + return vscode.Disposable.from( + selectProjectCommand, + { dispose: onGetInitialData }, + { dispose: onSelectProject }, + { dispose: effect1 }, + { dispose: effect2 }, + { dispose: effect3 }, + { dispose: effect4 }, + ); } /** Get the user to select a project */ diff --git a/firebase-vscode/src/firemat/index.ts b/firebase-vscode/src/firemat/index.ts index fb8f5b1a442..9dae6d9f02f 100644 --- a/firebase-vscode/src/firemat/index.ts +++ b/firebase-vscode/src/firemat/index.ts @@ -1,5 +1,5 @@ import vscode, { Disposable, ExtensionContext } from "vscode"; -import { signal } from "@preact/signals-core"; +import { effect, signal } from "@preact/signals-core"; import { ExtensionBrokerImpl } from "../extension-broker"; import { registerExecution } from "./execution"; @@ -13,6 +13,7 @@ import { import { registerConnectors } from "./connectors"; import { AuthService } from "../auth/service"; import { registerFirebaseDataConnectView } from "./firebase-data-connect"; +import { currentProjectId } from "../core/project"; // import { setupLanguageClient } from "./language-client"; const firematEndpoint = signal(undefined); @@ -40,7 +41,20 @@ export function registerFiremat( } }); + const selectedProjectStatus = vscode.window.createStatusBarItem( + vscode.StatusBarAlignment.Left, + ); + selectedProjectStatus.tooltip = "Select a Firebase project"; + selectedProjectStatus.command = "firebase.selectProject"; + return Disposable.from( + selectedProjectStatus, + { + dispose: effect(() => { + selectedProjectStatus.text = currentProjectId.value ?? ""; + selectedProjectStatus.show(); + }), + }, registerExecution(context, broker, firematService), registerExplorer(context, broker, firematService), registerFirebaseDataConnectView(context, broker), From c6c415a95376cda7912239c18e3373ecd2a7f40f Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Wed, 31 Jan 2024 05:53:24 +0100 Subject: [PATCH 7/9] Rename --- firebase-vscode/src/firemat/code-lens-provider.ts | 2 +- .../firemat/{firebase-data-connect.ts => connect-instance.ts} | 0 firebase-vscode/src/firemat/execution.ts | 2 +- firebase-vscode/src/firemat/index.ts | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename firebase-vscode/src/firemat/{firebase-data-connect.ts => connect-instance.ts} (100%) diff --git a/firebase-vscode/src/firemat/code-lens-provider.ts b/firebase-vscode/src/firemat/code-lens-provider.ts index 0098ea1ae78..c8bdc6a40ca 100644 --- a/firebase-vscode/src/firemat/code-lens-provider.ts +++ b/firebase-vscode/src/firemat/code-lens-provider.ts @@ -7,7 +7,7 @@ import { isFirematEmulatorRunning } from "../core/emulators"; import { Signal } from "@preact/signals-core"; import { firematConfig } from "../core/config"; import path from "path"; -import { selectedInstance } from "./firebase-data-connect"; +import { selectedInstance } from "./connect-instance"; abstract class ComputedCodeLensProvider implements vscode.CodeLensProvider { private readonly _onChangeCodeLensesEmitter = new vscode.EventEmitter(); diff --git a/firebase-vscode/src/firemat/firebase-data-connect.ts b/firebase-vscode/src/firemat/connect-instance.ts similarity index 100% rename from firebase-vscode/src/firemat/firebase-data-connect.ts rename to firebase-vscode/src/firemat/connect-instance.ts diff --git a/firebase-vscode/src/firemat/execution.ts b/firebase-vscode/src/firemat/execution.ts index cfc7f76fb78..48f6d256df8 100644 --- a/firebase-vscode/src/firemat/execution.ts +++ b/firebase-vscode/src/firemat/execution.ts @@ -21,7 +21,7 @@ import { OperationDefinitionNode, OperationTypeNode, print } from "graphql"; import { FirematService } from "./service"; import { FirematError, toSerializedError } from "../../common/error"; import { OperationLocation } from "./types"; -import { selectedInstance } from "./firebase-data-connect"; +import { selectedInstance } from "./connect-instance"; export function registerExecution( context: ExtensionContext, diff --git a/firebase-vscode/src/firemat/index.ts b/firebase-vscode/src/firemat/index.ts index 16be9b8ffaa..0b7432c86a8 100644 --- a/firebase-vscode/src/firemat/index.ts +++ b/firebase-vscode/src/firemat/index.ts @@ -13,7 +13,7 @@ import { import { globalSignal } from "../utils/globals"; import { registerConnectors } from "./connectors"; import { AuthService } from "../auth/service"; -import { registerFirebaseDataConnectView } from "./firebase-data-connect"; +import { registerFirebaseDataConnectView } from "./connect-instance"; import { currentProjectId } from "../core/project"; // import { setupLanguageClient } from "./language-client"; From 6614859f56b452607f8781957bcf57c2702fb933 Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Wed, 31 Jan 2024 05:55:51 +0100 Subject: [PATCH 8/9] Extract functions --- .../src/firemat/connect-instance.ts | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/firebase-vscode/src/firemat/connect-instance.ts b/firebase-vscode/src/firemat/connect-instance.ts index ed17ad83491..91e51b4140f 100644 --- a/firebase-vscode/src/firemat/connect-instance.ts +++ b/firebase-vscode/src/firemat/connect-instance.ts @@ -31,6 +31,28 @@ export function registerFirebaseDataConnectView( selectedInstanceStatus.tooltip = "Select a Firebase instance"; selectedInstanceStatus.command = "firebase.firemat.connectToInstance"; + function syncStatusBarWithSelectedInstance() { + return effect(() => { + selectedInstanceStatus.text = selectedInstance.value ?? ""; + selectedInstanceStatus.show(); + }); + } + + // Handle cases where the emulator is the currently selected instance, + // and the emulator is stopped. + // This also initializes the selectedInstance value to the first instance. + function syncSelectedInstance() { + return effect(() => { + const isSelectedInstanceInOptions = instanceOptions.value?.includes( + selectedInstance.value, + ); + + if (!isSelectedInstanceInOptions) { + selectedInstance.value = instanceOptions.value?.[0]; + } + }); + } + return vscode.Disposable.from( vscode.commands.registerCommand( "firebase.firemat.connectToInstance", @@ -47,27 +69,8 @@ export function registerFirebaseDataConnectView( ), selectedInstanceStatus, - { - // Sync the status bar with the selected instance - dispose: effect(() => { - selectedInstanceStatus.text = selectedInstance.value ?? ""; - selectedInstanceStatus.show(); - }), - }, - { - // Handle cases where the emulator is the currently selected instance, - // and the emulator is stopped. - // This also initializes the selectedInstance value to the first instance. - dispose: effect(() => { - const isSelectedInstanceInOptions = instanceOptions.value?.includes( - selectedInstance.value, - ); - - if (!isSelectedInstanceInOptions) { - selectedInstance.value = instanceOptions.value?.[0]; - } - }), - }, + { dispose: syncStatusBarWithSelectedInstance() }, + { dispose: syncSelectedInstance() }, { dispose: broker.on("connectToInstance", async () => { vscode.commands.executeCommand("firebase.firemat.connectToInstance"); From 5a15fda0746ab550e054c80892f5f4fa710f92be Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Wed, 31 Jan 2024 05:56:27 +0100 Subject: [PATCH 9/9] Rename --- firebase-vscode/src/firemat/connect-instance.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firebase-vscode/src/firemat/connect-instance.ts b/firebase-vscode/src/firemat/connect-instance.ts index 91e51b4140f..49bff11e728 100644 --- a/firebase-vscode/src/firemat/connect-instance.ts +++ b/firebase-vscode/src/firemat/connect-instance.ts @@ -41,7 +41,7 @@ export function registerFirebaseDataConnectView( // Handle cases where the emulator is the currently selected instance, // and the emulator is stopped. // This also initializes the selectedInstance value to the first instance. - function syncSelectedInstance() { + function initializeSelectedInstance() { return effect(() => { const isSelectedInstanceInOptions = instanceOptions.value?.includes( selectedInstance.value, @@ -70,7 +70,7 @@ export function registerFirebaseDataConnectView( selectedInstanceStatus, { dispose: syncStatusBarWithSelectedInstance() }, - { dispose: syncSelectedInstance() }, + { dispose: initializeSelectedInstance() }, { dispose: broker.on("connectToInstance", async () => { vscode.commands.executeCommand("firebase.firemat.connectToInstance");