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

"Connect to Postgres" using ConfigureEmulator API #7262

Merged
merged 7 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
2 changes: 2 additions & 0 deletions firebase-vscode/common/messaging/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ export interface ExtensionToWebviewParamsMap {

notifyIsConnectedToPostgres: boolean;

notifyPostgresStringChanged: string;

/** Triggered when new environment variables values are found. */
notifyEnv: { env: { isMonospace: boolean } };

Expand Down
21 changes: 18 additions & 3 deletions firebase-vscode/src/core/emulators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export class EmulatorsController implements Disposable {
displayInfo: listRunningEmulators(),
},
};
this.emulatorStatusItem.text = "$(data-connect) Emulators: running";
this.emulatorStatusItem.text = "$(data-connect) Connected to local Postgres";

// data connect specifics; including temp logging implementation
if (
Expand Down Expand Up @@ -207,10 +207,10 @@ export class EmulatorsController implements Disposable {

// Updating the status bar label as "running", but don't "show" it.
// We only show the status bar item when explicitly by interacting with the sidebar.
this.emulatorStatusItem.text = "$(data-connect) Emulators: running";
this.emulatorStatusItem.text = "$(data-connect) Connected to local Postgres";
this.emulatorStatusItem.backgroundColor = undefined;
} catch (e) {
this.emulatorStatusItem.text = "Emulators: errored";
this.emulatorStatusItem.text = "$(data-connect) Emulators: errored";
this.emulatorStatusItem.backgroundColor = new ThemeColor(
"statusBarItem.errorBackground",
);
Expand Down Expand Up @@ -241,6 +241,21 @@ export class EmulatorsController implements Disposable {
};
}

public getLocalEndpoint = () => computed<string | undefined>(() => {
const emulatorInfos = this.emulators.value.infos?.displayInfo;
const dataConnectEmulator = emulatorInfos?.find(
(emulatorInfo) => emulatorInfo.name === Emulators.DATACONNECT,
);

if (!dataConnectEmulator) {
return undefined;
}

return (
"http://" + dataConnectEmulator.host + ":" + dataConnectEmulator.port
Copy link
Member

Choose a reason for hiding this comment

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

Please handle IPv6 quoting similar to

if (info.host.includes(":")) {
url.hostname = `[${info.host}]`; // IPv6 addresses need to be quoted.

);
});

dispose(): void {
this.stopEmulators();
this.subscriptions.forEach((subscription) => subscription());
Expand Down
2 changes: 1 addition & 1 deletion firebase-vscode/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export async function registerCore(
"firebase.openFirebaseRc",
() => {
for (const root of getRootFolders()) {
upsertFile(vscode.Uri.file(".firebaserc"), () => "");
upsertFile(vscode.Uri.file(`${root}/.firebaserc`), () => "");
}
},
);
Expand Down
45 changes: 25 additions & 20 deletions firebase-vscode/src/data-connect/emulator.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import { EmulatorsController } from "../core/emulators";
import * as vscode from "vscode";
import { ExtensionBrokerImpl } from "../extension-broker";
import { Signal, effect, signal } from "@preact/signals-core";
import { ReadonlySignal, effect, signal } from "@preact/signals-core";
import { RC } from "../rc";
import { firebaseRC, updateFirebaseRCProject } from "../core/config";
import { DataConnectEmulatorClient } from "../../../src/emulator/dataconnectEmulator";
import { firstWhereDefined } from "../utils/signal";

/** FDC-specific emulator logic */
export class DataConnectEmulatorController implements vscode.Disposable {
constructor(
readonly emulatorsController: EmulatorsController,
broker: ExtensionBrokerImpl,
readonly broker: ExtensionBrokerImpl,
) {
function notifyIsConnectedToPostgres(isConnected: boolean) {
broker.send("notifyIsConnectedToPostgres", isConnected);
}

this.subs.push(
broker.on("connectToPostgres", () => this.connectToPostgres()),
broker.on("disconnectPostgres", () => this.disconnectPostgres()),

// Notify webviews when the emulator status changes
effect(() => {
Expand All @@ -34,40 +35,44 @@ export class DataConnectEmulatorController implements vscode.Disposable {
readonly isPostgresEnabled = signal(false);
private readonly subs: Array<() => void> = [];

private async promptConnectionString(): Promise<string | undefined> {
private async promptConnectionString(
defaultConnectionString: string,
): Promise<string | undefined> {
const connectionString = await vscode.window.showInputBox({
title: "Enter a Postgres connection string",
prompt:
"A Postgres database must be configured to use the emulator locally. ",
placeHolder: "postgres://user:password@localhost:5432/dbname",
"A Postgres database must be configured to use the emulator locally.",
value: defaultConnectionString,
});

return connectionString;
}

private async connectToPostgres() {
const rc = firebaseRC.value?.tryReadValue;
const newConnectionString = await this.promptConnectionString(
rc?.getDataconnect()?.postgres.localConnectionString ||
"postgres://user:password@localhost:5432/dbname",
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we use switch the default string to point to the 'postgres' DB?

Suggested change
"postgres://user:password@localhost:5432/dbname",
"postgres://user:password@localhost:5432/postgres",

Copy link
Member

Choose a reason for hiding this comment

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

yes

);
if (!newConnectionString) {
return;
}
this.broker.send("notifyPostgresStringChanged", newConnectionString);

if (!rc?.getDataconnect()?.postgres) {
const newConnectionString = await this.promptConnectionString();
if (!newConnectionString) {
return;
}
updateFirebaseRCProject({
fdcPostgresConnectionString: newConnectionString,
});

updateFirebaseRCProject({
fdcPostgresConnectionString: newConnectionString,
});
}
// configure the emulator to use the local psql string
const emulatorClient = new DataConnectEmulatorClient(
await firstWhereDefined(this.emulatorsController.getLocalEndpoint()),
);
emulatorClient.configureEmulator({ connectionString: newConnectionString });

this.isPostgresEnabled.value = true;
this.emulatorsController.emulatorStatusItem.show();
}

private disconnectPostgres() {
this.isPostgresEnabled.value = false;
this.emulatorsController.emulatorStatusItem.hide();
}

dispose() {
this.subs.forEach((sub) => sub());
}
Expand Down
6 changes: 3 additions & 3 deletions firebase-vscode/src/data-connect/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,14 +185,14 @@ export function registerFdc(
context.subscriptions.push({
dispose: effect(() => {
const configs = dataConnectConfigs.value?.tryReadValue;
if (configs && fdcService.localEndpoint.value) {
if (configs && emulatorController.getLocalEndpoint().value) {
// TODO move to client.start or setupLanguageClient
vscode.commands.executeCommand("fdc-graphql.restart");
vscode.commands.executeCommand(
"firebase.dataConnect.executeIntrospection",
);
runEmulatorIssuesStream(configs, fdcService.localEndpoint.value);
runDataConnectCompiler(configs, fdcService.localEndpoint.value);
runEmulatorIssuesStream(configs, emulatorController.getLocalEndpoint().value);
runDataConnectCompiler(configs, emulatorController.getLocalEndpoint().value);
}
}),
});
Expand Down
45 changes: 25 additions & 20 deletions firebase-vscode/src/data-connect/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from "../dataconnect/types";
import { ClientResponse } from "../apiv2";
import { InstanceType } from "./code-lens-provider";
import { pluginLogger } from "../logger-wrapper";

/**
* DataConnect Emulator service
Expand All @@ -34,22 +35,6 @@ export class DataConnectService {
private emulatorsController: EmulatorsController,
) {}

readonly localEndpoint = computed<string | undefined>(() => {
const emulatorInfos =
this.emulatorsController.emulators.value.infos?.displayInfo;
const dataConnectEmulator = emulatorInfos?.find(
(emulatorInfo) => emulatorInfo.name === Emulators.DATACONNECT,
);

if (!dataConnectEmulator) {
return undefined;
}

return (
"http://" + dataConnectEmulator.host + ":" + dataConnectEmulator.port
);
});

async servicePath(
path: string,
instance: InstanceType,
Expand Down Expand Up @@ -192,7 +177,7 @@ export class DataConnectService {
return { data: (introspectionResults as any).data };
} catch (e) {
// TODO: surface error that emulator is not connected
console.error("error: ", e);
pluginLogger.error("error: ", e);
return { data: undefined };
}
}
Expand All @@ -214,7 +199,7 @@ export class DataConnectService {
extensions: {}, // Introspection is the only caller of executeGraphqlRead
});
const resp = await fetch(
(await firstWhereDefined(this.localEndpoint)) +
(await firstWhereDefined(this.emulatorsController.getLocalEndpoint())) +
`/v1alpha/projects/p/locations/l/services/${serviceId}:executeGraphqlRead`,
{
method: "POST",
Expand All @@ -230,7 +215,7 @@ export class DataConnectService {
return result;
} catch (e) {
// TODO: actual error handling
console.log(e);
pluginLogger.error(e);
return null;
}
}
Expand Down Expand Up @@ -265,7 +250,7 @@ export class DataConnectService {
return this.handleProdResponse(resp);
} else {
const resp = await fetch(
(await firstWhereDefined(this.localEndpoint)) +
(await firstWhereDefined(this.emulatorsController.getLocalEndpoint())) +
`/v1alpha/${servicePath}:executeGraphql`,
{
method: "POST",
Expand All @@ -280,4 +265,24 @@ export class DataConnectService {
return this.handleResponse(resp);
}
}

async connectToPostgres(connectionString: string): Promise<boolean> {
try {
await fetch(
firstWhereDefined(this.emulatorsController.getLocalEndpoint()) +
`/emulator/configure?connectionString=${connectionString}`,
{
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"x-mantle-admin": "all",
},
},
);
return true;
} catch (e: any) {
pluginLogger.error(e);
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ firebaseTest("When emulators are running, lists them", async function () {
await commands.waitEmulators();

expect(await statusBar.emulatorsStatus.getText()).toContain(
"Emulators: running"
"Connected to local Postgres"
);
});
30 changes: 17 additions & 13 deletions firebase-vscode/webviews/data-connect.entry.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from "react";
import { createRoot } from "react-dom/client";
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react";
import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react";
import { Spacer } from "./components/ui/Spacer";
import styles from "./globals/index.scss";
import { broker, useBroker } from "./globals/html-broker";
import { broker, useBroker, useBrokerListener } from "./globals/html-broker";
import { PanelSection } from "./components/ui/PanelSection";

// Prevent webpack from removing the `style` import above
Expand All @@ -18,23 +18,27 @@ function DataConnect() {
initialRequest: "getInitialIsConnectedToPostgres",
}) ?? false;

const psqlString = useBroker("notifyPostgresStringChanged");

return (
<>
<PanelSection title="Emulator">
<p>
Start the FDC emulator. See also:{" "}
<a href="https://firebase.google.com/docs/data-connect/quickstart">
Working with the emulator
</a>
</p>
<PanelSection title="Local Development">
{!isConnectedToPostgres && (<p>
Connect to Local PostgreSQL. See also:{" "}
<a href="https://firebase.google.com/docs/data-connect/quickstart">
Working with the emulator
</a>
</p>)
}
<Spacer size="xsmall" />
{isConnectedToPostgres ? (
<VSCodeButton onClick={() => broker.send("disconnectPostgres")}>
Stop emulator
</VSCodeButton>
<>
<label>Local emulator connected to:</label>
<VSCodeTextField disabled value={psqlString}></VSCodeTextField>
</>
) : (
<VSCodeButton onClick={() => broker.send("connectToPostgres")}>
Start emulator
Connect to Local PostgreSQL
</VSCodeButton>
)}
</PanelSection>
Expand Down
Loading
Loading