Compare commits

..

8 Commits

Author SHA1 Message Date
Victor Meng
f18e5dd218 Hardcode API type to Postgres 2022-10-06 00:23:26 -07:00
Victor Meng
a0911d92a6 Fix typing 2022-10-05 23:48:26 -07:00
Victor Meng
3b790ed3e2 Hardcode postgres endpoint 2022-10-05 23:23:14 -07:00
Victor Meng
8ad426c82f Merge branch 'master' of https://github.com/Azure/cosmos-explorer into PSQL_shell_integration 2022-10-05 23:21:37 -07:00
Victor Meng
3f24a57974 Make PSQL shell work end-to-end 2022-10-05 23:19:24 -07:00
Victor Meng
83e78ef9ac Merge branch 'master' of https://github.com/Azure/cosmos-explorer into PSQL_shell_integration 2022-10-05 17:35:41 -07:00
Victor Meng
c683d203b2 Close quick start tab when user types \q in PSQL shell 2022-09-29 23:45:17 -07:00
Victor Meng
ec74c93670 Integrate PSQL shell in quick start guide 2022-09-29 23:01:19 -07:00
12 changed files with 55 additions and 179 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -567,16 +567,6 @@ export interface ContainerConnectionInfo {
//need to add ram and rom info
}
export interface PostgresFirewallRule {
id: string;
name: string;
type: string;
properties: {
startIpAddress: string;
endIpAddress: string;
};
}
export enum PhoenixErrorType {
MaxAllocationTimeExceeded = "MaxAllocationTimeExceeded",
MaxDbAccountsPerUserExceeded = "MaxDbAccountsPerUserExceeded",

View File

@@ -36,7 +36,6 @@ export enum MessageTypes {
CloseTab,
OpenQuickstartBlade,
OpenPostgreSQLPasswordReset,
OpenPostgresNetworkingBlade,
}
export { Versions, ActionContracts, Diagnostics };

View File

@@ -77,7 +77,7 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
} else if (StringUtils.endsWith(notebookServerEndpoint, "cassandra")) {
terminalEndpoint = this.props.databaseAccount?.properties.cassandraEndpoint;
} else if (StringUtils.endsWith(notebookServerEndpoint, "postgresql")) {
return this.props.databaseAccount?.properties.postgresqlEndpoint;
return "c.vimeng-postgre-citus.postgres.database.azure.com";
}
if (terminalEndpoint) {

View File

@@ -1,22 +0,0 @@
import { Image, PrimaryButton, Stack, Text } from "@fluentui/react";
import { sendMessage } from "Common/MessageHandler";
import { MessageTypes } from "Contracts/ExplorerContracts";
import React from "react";
import FirewallRuleScreenshot from "../../../images/firewallRule.png";
export const QuickstartFirewallNotification: React.FC = (): JSX.Element => (
<Stack style={{ padding: "16px 20px" }}>
<Text block>
To use the PostgreSQL shell, you need to add a firewall rule to allow access from all IP addresses
(0.0.0.0-255.255.255).
</Text>
<Text block>We strongly recommend removing this rule once you finish using the PostgreSQL shell.</Text>
<Image style={{ margin: "20px 0" }} src={FirewallRuleScreenshot} />
<PrimaryButton
style={{ width: 150 }}
onClick={() => sendMessage({ type: MessageTypes.OpenPostgresNetworkingBlade })}
>
Add firewall rule
</PrimaryButton>
</Stack>
);

View File

@@ -109,12 +109,8 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
<br />
<br />
To begin, please enter the cluster&apos;s password in the PostgreSQL terminal.
<br />
<br />
Note: If you navigate out of the Quick Start tab (PostgreSQL Shell), the session will be closed and
all ongoing commands might be interrupted.
</Text>
<Youtube videoId="nT64dFSfiUo" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
<Youtube videoId="UaBDXHMQAUw" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
</Stack>
</PivotItem>
<PivotItem
@@ -124,7 +120,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
onClick={() => setCurrentStep(1)}
>
<Stack style={{ marginTop: 20 }}>
<Text>Let&apos;s create two tables github_users and github_events in cosmosdb_tutorial schema.</Text>
<Text>Lets create two tables github_users and github_events in cosmosdb_tutorial schema.</Text>
<DefaultButton
style={{ marginTop: 16, width: 150 }}
onClick={() => useTerminal.getState().sendMessage(newTableCommand)}
@@ -154,7 +150,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
onClick={() => onCopyBtnClicked("#newTableCommand")}
/>
</Stack>
<Youtube videoId="il_sA6U1WcY" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
<Youtube videoId="VJqupvSQ-mw" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
</Stack>
</PivotItem>
<PivotItem
@@ -199,7 +195,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
onClick={() => onCopyBtnClicked("#distributeTableCommand")}
/>
</Stack>
<Youtube videoId="kCCDRRrN1r0" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
<Youtube videoId="Q-AW7q1GLDY" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
</Stack>
</PivotItem>
<PivotItem
@@ -239,7 +235,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
onClick={() => onCopyBtnClicked("#loadDataCommand")}
/>
</Stack>
<Youtube videoId="XSMEE2tujEk" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
<Youtube videoId="h15fvLKXzRo" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
</Stack>
</PivotItem>
<PivotItem
@@ -281,7 +277,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
onClick={() => onCopyBtnClicked("#queryCommand")}
/>
</Stack>
<Youtube videoId="k_EanjMtaPg" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
<Youtube videoId="p46nRnE4b8Y" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
</Stack>
</PivotItem>
</Pivot>

View File

@@ -186,10 +186,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
headline="Create your password"
target={"#mainButton-quickstartDescription"}
hasCloseButton
onDismiss={() => {
localStorage.setItem(userContext.databaseAccount.id, "true");
usePostgres.getState().setShowResetPasswordBubble(false);
}}
onDismiss={() => usePostgres.getState().setShowResetPasswordBubble(false)}
calloutProps={{
directionalHint: DirectionalHint.bottomRightEdge,
directionalHintFixed: true,
@@ -200,15 +197,14 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
primaryButtonProps={{
text: "Create",
onClick: () => {
localStorage.setItem(userContext.databaseAccount.id, "true");
sendMessage({
type: MessageTypes.OpenPostgreSQLPasswordReset,
type: MessageTypes.OpenQuickstartBlade,
});
usePostgres.getState().setShowResetPasswordBubble(false);
},
}}
>
If you haven&apos;t changed your password yet, change it now.
This password will be used to connect to the database.
</TeachingBubble>
)}
</div>
@@ -351,10 +347,10 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
const connectBtn = {
iconSrc: ConnectIcon,
title: userContext.apiType === "Postgres" ? "Connect with pgAdmin" : "Connect",
title: userContext.apiType === "Postgres" ? "Connect with PG Admin" : "Connect",
description:
userContext.apiType === "Postgres"
? "Prefer pgAdmin? Find your connection strings here"
? "Prefer PGadmin? Find your connection strings here"
: "Prefer using your own choice of tooling? Find the connection string you need to connect",
onClick: () => useTabs.getState().openAndActivateReactTab(ReactTabKind.Connect),
};

View File

@@ -120,7 +120,7 @@ You can enable or disable public IP addresses on the worker nodes on 'Networking
</Stack>
<Label>Secure connections</Label>
<Text style={{ marginBottom: 8 }}>
<Text>
Only secure connections are supported. For production use cases, we recommend using the &apos;verify-full&apos;
mode to enforce TLS certificate verification. You will need to download the Hyperscale (Citus) certificate, and
provide it when connecting to the database.{" "}
@@ -128,18 +128,6 @@ You can enable or disable public IP addresses on the worker nodes on 'Networking
Learn more
</Link>
</Text>
<Label>Connect with pgAdmin</Label>
<Text>
Refer to our{" "}
<Link
href="https://learn.microsoft.com/en-us/azure/postgresql/hyperscale/howto-connect?tabs=pgadmin"
target="_blank"
>
guide
</Link>{" "}
to help you connect via pgAdmin.
</Text>
</div>
);
};

View File

@@ -1,15 +1,11 @@
import { Spinner, SpinnerSize, Stack } from "@fluentui/react";
import { configContext } from "ConfigContext";
import { NotebookWorkspaceConnectionInfo, PostgresFirewallRule } from "Contracts/DataModels";
import { NotebookWorkspaceConnectionInfo } from "Contracts/DataModels";
import { NotebookTerminalComponent } from "Explorer/Controls/Notebook/NotebookTerminalComponent";
import Explorer from "Explorer/Explorer";
import { useNotebook } from "Explorer/Notebook/useNotebook";
import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFirewallNotification";
import { QuickstartGuide } from "Explorer/Quickstart/QuickstartGuide";
import { ReactTabKind, useTabs } from "hooks/useTabs";
import React, { useEffect, useState } from "react";
import React, { useEffect } from "react";
import { userContext } from "UserContext";
import { armRequest } from "Utils/arm/request";
interface QuickstartTabProps {
explorer: Explorer;
@@ -17,58 +13,29 @@ interface QuickstartTabProps {
export const QuickstartTab: React.FC<QuickstartTabProps> = ({ explorer }: QuickstartTabProps): JSX.Element => {
const notebookServerInfo = useNotebook((state) => state.notebookServerInfo);
const [isAllPublicIPAddressEnabled, setIsAllPublicIPAddressEnabled] = useState<boolean>(true);
useEffect(() => {
explorer.allocateContainer();
}, []);
const getNotebookServerInfo = (): NotebookWorkspaceConnectionInfo => ({
authToken: notebookServerInfo.authToken,
notebookServerEndpoint: `${notebookServerInfo.notebookServerEndpoint?.replace(/\/+$/, "")}/postgresql`,
forwardingId: notebookServerInfo.forwardingId,
});
const checkFirewallRules = async (): Promise<void> => {
const firewallRulesUri = `${userContext.databaseAccount.id}/firewallRules`;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const response: any = await armRequest({
host: configContext.ARM_ENDPOINT,
path: firewallRulesUri,
method: "GET",
apiVersion: "2020-10-05-privatepreview",
});
const firewallRules: PostgresFirewallRule[] = response?.data?.value || response?.value || [];
const isEnabled = firewallRules.some(
(rule) => rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255"
);
setIsAllPublicIPAddressEnabled(isEnabled);
// If the firewall rule is not added, check every 30 seconds to see if the user has added the rule
if (!isEnabled && useTabs.getState().activeReactTab === ReactTabKind.Quickstart) {
setTimeout(checkFirewallRules, 30000);
}
};
useEffect(() => {
checkFirewallRules();
});
useEffect(() => {
explorer.allocateContainer();
}, []);
return (
<Stack style={{ width: "100%" }} horizontal>
<Stack style={{ width: "50%" }}>
<QuickstartGuide />
</Stack>
<Stack style={{ width: "50%", borderLeft: "black solid 1px" }}>
{!isAllPublicIPAddressEnabled && <QuickstartFirewallNotification />}
{isAllPublicIPAddressEnabled && notebookServerInfo?.notebookServerEndpoint && (
{notebookServerInfo?.notebookServerEndpoint && (
<NotebookTerminalComponent
notebookServerInfo={getNotebookServerInfo()}
databaseAccount={userContext.databaseAccount}
tabId="QuickstartPSQLShell"
/>
)}
{isAllPublicIPAddressEnabled && !notebookServerInfo?.notebookServerEndpoint && (
{!notebookServerInfo?.notebookServerEndpoint && (
<Spinner styles={{ root: { marginTop: 10 } }} size={SpinnerSize.large}></Spinner>
)}
</Stack>

View File

@@ -1,9 +1,6 @@
import { Spinner, SpinnerSize } from "@fluentui/react";
import { configContext } from "ConfigContext";
import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFirewallNotification";
import * as ko from "knockout";
import * as React from "react";
import { armRequest } from "Utils/arm/request";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
@@ -29,15 +26,10 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
constructor(
private getNotebookServerInfo: () => DataModels.NotebookWorkspaceConnectionInfo,
private getDatabaseAccount: () => DataModels.DatabaseAccount,
private getTabId: () => string,
private isAllPublicIPAddressesEnabled: ko.Observable<boolean>
private getTabId: () => string
) {}
public renderComponent(): JSX.Element {
if (!this.isAllPublicIPAddressesEnabled()) {
return <QuickstartFirewallNotification />;
}
return this.parameters() ? (
<NotebookTerminalComponent
notebookServerInfo={this.getNotebookServerInfo()}
@@ -54,33 +46,25 @@ export default class TerminalTab extends TabsBase {
public readonly html = '<div style="height: 100%" data-bind="react:notebookTerminalComponentAdapter"></div> ';
private container: Explorer;
private notebookTerminalComponentAdapter: NotebookTerminalComponentAdapter;
private isAllPublicIPAddressesEnabled: ko.Observable<boolean>;
constructor(options: TerminalTabOptions) {
super(options);
this.container = options.container;
this.isAllPublicIPAddressesEnabled = ko.observable(true);
this.notebookTerminalComponentAdapter = new NotebookTerminalComponentAdapter(
() => this.getNotebookServerInfo(options),
() => userContext?.databaseAccount,
() => this.tabId,
this.isAllPublicIPAddressesEnabled
() => this.tabId
);
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
if (
this.isTemplateReady() &&
useNotebook.getState().isNotebookEnabled &&
useNotebook.getState().notebookServerInfo?.notebookServerEndpoint &&
this.isAllPublicIPAddressesEnabled()
useNotebook.getState().notebookServerInfo?.notebookServerEndpoint
) {
return true;
}
return false;
});
if (options.kind === ViewModels.TerminalKind.Postgres) {
this.checkPostgresFirewallRules();
}
}
public getContainer(): Explorer {
@@ -126,25 +110,4 @@ export default class TerminalTab extends TabsBase {
forwardingId: info.forwardingId,
};
}
private async checkPostgresFirewallRules(): Promise<void> {
const firewallRulesUri = `${userContext.databaseAccount.id}/firewallRules`;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const response: any = await armRequest({
host: configContext.ARM_ENDPOINT,
path: firewallRulesUri,
method: "GET",
apiVersion: "2020-10-05-privatepreview",
});
const firewallRules: DataModels.PostgresFirewallRule[] = response?.data?.value || response?.value || [];
const isEnabled = firewallRules.some(
(rule) => rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255"
);
this.isAllPublicIPAddressesEnabled(isEnabled);
// If the firewall rule is not added, check every 30 seconds to see if the user has added the rule
if (!isEnabled) {
setTimeout(() => this.checkPostgresFirewallRules(), 30000);
}
}
}

View File

@@ -42,7 +42,7 @@ const createServerSettings = (props: TerminalProps): ServerConnection.ISettings
return ServerConnection.makeSettings(options);
};
const initTerminal = async (props: TerminalProps): Promise<ITerminalConnection | undefined> => {
const initTerminal = async (props: TerminalProps): Promise<ITerminalConnection> => {
// Initialize userContext (only properties which are needed by TelemetryProcessor)
updateUserContext({
subscriptionId: props.subscriptionId,

View File

@@ -109,19 +109,16 @@ function updateUserContext(newContext: Partial<UserContext>): void {
);
if (!localStorage.getItem(newContext.databaseAccount.id)) {
if (newContext.isTryCosmosDBSubscription || isNewAccount) {
if (newContext.apiType === "Postgres") {
usePostgres.getState().setShowResetPasswordBubble(true);
usePostgres.getState().setShowPostgreTeachingBubble(true);
} else {
useCarousel.getState().setShouldOpen(true);
localStorage.setItem(newContext.databaseAccount.id, "true");
traceOpen(Action.OpenCarousel);
}
} else if (newContext.apiType === "Postgres") {
if (newContext.apiType === "Postgres") {
usePostgres.getState().setShowPostgreTeachingBubble(true);
localStorage.setItem(newContext.databaseAccount.id, "true");
}
if (userContext.isTryCosmosDBSubscription || isNewAccount) {
useCarousel.getState().setShouldOpen(true);
usePostgres.getState().setShowResetPasswordBubble(true);
localStorage.setItem(newContext.databaseAccount.id, "true");
traceOpen(Action.OpenCarousel);
}
}
}
Object.assign(userContext, newContext);
@@ -132,28 +129,30 @@ function apiType(account: DatabaseAccount | undefined): ApiType {
return "SQL";
}
const capabilities = account.properties?.capabilities;
if (capabilities) {
if (capabilities.find((c) => c.name === "EnableCassandra")) {
return "Cassandra";
}
if (capabilities.find((c) => c.name === "EnableGremlin")) {
return "Gremlin";
}
if (capabilities.find((c) => c.name === "EnableMongo")) {
return "Mongo";
}
if (capabilities.find((c) => c.name === "EnableTable")) {
return "Tables";
}
}
if (account.kind === "MongoDB" || account.kind === "Parse") {
return "Mongo";
}
if (account.kind === "Postgres") {
return "Postgres";
}
return "SQL";
return "Postgres";
// const capabilities = account.properties?.capabilities;
// if (capabilities) {
// if (capabilities.find((c) => c.name === "EnableCassandra")) {
// return "Cassandra";
// }
// if (capabilities.find((c) => c.name === "EnableGremlin")) {
// return "Gremlin";
// }
// if (capabilities.find((c) => c.name === "EnableMongo")) {
// return "Mongo";
// }
// if (capabilities.find((c) => c.name === "EnableTable")) {
// return "Tables";
// }
// }
// if (account.kind === "MongoDB" || account.kind === "Parse") {
// return "Mongo";
// }
// if (account.kind === "Postgres") {
// return "Postgres";
// }
// return "SQL";
}
export { userContext, updateUserContext };