Add support for psql shell (#1324)

Add support for psql shell
- add new terminal type
- handle missing documentEndpoint property
This commit is contained in:
Armando Trejo Oliver 2022-09-22 15:39:35 -07:00 committed by GitHub
parent 2e618cb3c4
commit 3abbb63adc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 68 additions and 16 deletions

View File

@ -372,6 +372,7 @@ export enum TerminalKind {
Default = 0, Default = 0,
Mongo = 1, Mongo = 1,
Cassandra = 2, Cassandra = 2,
Postgres = 3,
} }
export interface DataExplorerInputsFrame { export interface DataExplorerInputsFrame {

View File

@ -74,6 +74,8 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
this.props.databaseAccount?.properties.mongoEndpoint || this.props.databaseAccount?.properties.documentEndpoint; this.props.databaseAccount?.properties.mongoEndpoint || this.props.databaseAccount?.properties.documentEndpoint;
} else if (StringUtils.endsWith(notebookServerEndpoint, "cassandra")) { } else if (StringUtils.endsWith(notebookServerEndpoint, "cassandra")) {
terminalEndpoint = this.props.databaseAccount?.properties.cassandraEndpoint; terminalEndpoint = this.props.databaseAccount?.properties.cassandraEndpoint;
} else if (StringUtils.endsWith(notebookServerEndpoint, "postgresql")) {
return (this.props.databaseAccount?.properties as any).postgresqlEndpoint;
} }
if (terminalEndpoint) { if (terminalEndpoint) {

View File

@ -35,6 +35,7 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"phoenixClient": PhoenixClient { "phoenixClient": PhoenixClient {
"armResourceId": undefined,
"retryOptions": Object { "retryOptions": Object {
"maxTimeout": 5000, "maxTimeout": 5000,
"minTimeout": 5000, "minTimeout": 5000,
@ -111,6 +112,7 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"phoenixClient": PhoenixClient { "phoenixClient": PhoenixClient {
"armResourceId": undefined,
"retryOptions": Object { "retryOptions": Object {
"maxTimeout": 5000, "maxTimeout": 5000,
"minTimeout": 5000, "minTimeout": 5000,

View File

@ -93,7 +93,8 @@ export default class Explorer {
dataExplorerArea: Constants.Areas.ResourceTree, dataExplorerArea: Constants.Areas.ResourceTree,
}); });
this._isInitializingNotebooks = false; this._isInitializingNotebooks = false;
this.phoenixClient = new PhoenixClient();
this.phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
useNotebook.subscribe( useNotebook.subscribe(
() => this.refreshCommandBarButtons(), () => this.refreshCommandBarButtons(),
(state) => state.isNotebooksEnabledForAccount (state) => state.isNotebooksEnabledForAccount
@ -353,7 +354,7 @@ export default class Explorer {
(notebookServerInfo && notebookServerInfo.notebookServerEndpoint === undefined)) (notebookServerInfo && notebookServerInfo.notebookServerEndpoint === undefined))
) { ) {
const provisionData: IProvisionData = { const provisionData: IProvisionData = {
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint, cosmosEndpoint: userContext?.databaseAccount?.properties?.documentEndpoint,
poolId: PoolIdType.DefaultPoolId, poolId: PoolIdType.DefaultPoolId,
}; };
const connectionStatus: ContainerConnectionInfo = { const connectionStatus: ContainerConnectionInfo = {
@ -1058,6 +1059,10 @@ export default class Explorer {
title = "Cassandra Shell"; title = "Cassandra Shell";
break; break;
case ViewModels.TerminalKind.Postgres:
title = "PSQL Shell";
break;
default: default:
throw new Error("Terminal kind: ${kind} not supported"); throw new Error("Terminal kind: ${kind} not supported");
} }

View File

@ -85,11 +85,14 @@ export function createStaticCommandBarButtons(
(userContext.apiType === "Mongo" && (userContext.apiType === "Mongo" &&
useNotebook.getState().isShellEnabled && useNotebook.getState().isShellEnabled &&
selectedNodeState.isDatabaseNodeOrNoneSelected()) || selectedNodeState.isDatabaseNodeOrNoneSelected()) ||
userContext.apiType === "Cassandra" userContext.apiType === "Cassandra" ||
userContext.apiType === "Postgres"
) { ) {
notebookButtons.push(createDivider()); notebookButtons.push(createDivider());
if (userContext.apiType === "Cassandra") { if (userContext.apiType === "Cassandra") {
notebookButtons.push(createOpenCassandraTerminalButton(container)); notebookButtons.push(createOpenCassandraTerminalButton(container));
} else if (userContext.apiType === "Postgres") {
notebookButtons.push(createOpenPsqlTerminalButton(container));
} else { } else {
notebookButtons.push(createOpenMongoTerminalButton(container)); notebookButtons.push(createOpenMongoTerminalButton(container));
} }
@ -523,6 +526,28 @@ function createOpenCassandraTerminalButton(container: Explorer): CommandButtonCo
}; };
} }
function createOpenPsqlTerminalButton(container: Explorer): CommandButtonComponentProps {
const label = "Open PSQL Shell";
const disableButton =
!useNotebook.getState().isNotebooksEnabledForAccount && !useNotebook.getState().isNotebookEnabled;
return {
iconSrc: HostedTerminalIcon,
iconAlt: label,
onCommandClick: () => {
if (useNotebook.getState().isNotebookEnabled) {
container.openNotebookTerminal(ViewModels.TerminalKind.Postgres);
}
},
commandButtonLabel: label,
hasPopup: false,
disabled: disableButton,
ariaLabel: label,
tooltipText: !disableButton
? ""
: "This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.",
};
}
function createNotebookWorkspaceResetButton(container: Explorer): CommandButtonComponentProps { function createNotebookWorkspaceResetButton(container: Explorer): CommandButtonComponentProps {
const label = "Reset Workspace"; const label = "Reset Workspace";
return { return {

View File

@ -23,7 +23,7 @@ export class NotebookContainerClient {
private scheduleTimerId: NodeJS.Timeout; private scheduleTimerId: NodeJS.Timeout;
constructor(private onConnectionLost: () => void) { constructor(private onConnectionLost: () => void) {
this.phoenixClient = new PhoenixClient(); this.phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
this.retryOptions = { this.retryOptions = {
retries: Notebook.retryAttempts, retries: Notebook.retryAttempts,
maxTimeout: Notebook.retryAttemptDelayMs, maxTimeout: Notebook.retryAttemptDelayMs,

View File

@ -307,7 +307,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
let isPhoenixFeatures = false; let isPhoenixFeatures = false;
const isPublicInternetAllowed = isPublicInternetAccessAllowed(); const isPublicInternetAllowed = isPublicInternetAccessAllowed();
const phoenixClient = new PhoenixClient(); const phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
const dbAccountAllowedInfo = await phoenixClient.getDbAccountAllowedStatus(); const dbAccountAllowedInfo = await phoenixClient.getDbAccountAllowedStatus();
if (dbAccountAllowedInfo.status === HttpStatusCodes.OK) { if (dbAccountAllowedInfo.status === HttpStatusCodes.OK) {

View File

@ -24,6 +24,7 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"phoenixClient": PhoenixClient { "phoenixClient": PhoenixClient {
"armResourceId": undefined,
"retryOptions": Object { "retryOptions": Object {
"maxTimeout": 5000, "maxTimeout": 5000,
"minTimeout": 5000, "minTimeout": 5000,

View File

@ -14,6 +14,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"phoenixClient": PhoenixClient { "phoenixClient": PhoenixClient {
"armResourceId": undefined,
"retryOptions": Object { "retryOptions": Object {
"maxTimeout": 5000, "maxTimeout": 5000,
"minTimeout": 5000, "minTimeout": 5000,

View File

@ -95,6 +95,10 @@ export default class TerminalTab extends TabsBase {
endpointSuffix = "cassandra"; endpointSuffix = "cassandra";
break; break;
case ViewModels.TerminalKind.Postgres:
endpointSuffix = "postgresql";
break;
default: default:
throw new Error(`Terminal kind: ${options.kind} not supported`); throw new Error(`Terminal kind: ${options.kind} not supported`);
} }

View File

@ -32,6 +32,7 @@ import { userContext } from "../UserContext";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils"; import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
export class PhoenixClient { export class PhoenixClient {
private armResourceId: string;
private containerHealthHandler: NodeJS.Timeout; private containerHealthHandler: NodeJS.Timeout;
private retryOptions: promiseRetry.Options = { private retryOptions: promiseRetry.Options = {
retries: Notebook.retryAttempts, retries: Notebook.retryAttempts,
@ -39,6 +40,10 @@ export class PhoenixClient {
minTimeout: Notebook.retryAttemptDelayMs, minTimeout: Notebook.retryAttemptDelayMs,
}; };
constructor(armResourceId: string) {
this.armResourceId = armResourceId;
}
public async allocateContainer(provisionData: IProvisionData): Promise<IResponse<IPhoenixServiceInfo>> { public async allocateContainer(provisionData: IProvisionData): Promise<IResponse<IPhoenixServiceInfo>> {
return this.executeContainerAssignmentOperation(provisionData, "allocate"); return this.executeContainerAssignmentOperation(provisionData, "allocate");
} }
@ -214,22 +219,21 @@ export class PhoenixClient {
} }
} }
public static getPhoenixEndpoint(): string { private getPhoenixControlPlanePathPrefix(): string {
const phoenixEndpoint = if (!this.armResourceId) {
throw new Error("The Phoenix client was not initialized properly: missing ARM resourcce id");
}
const toolsEndpoint =
userContext.features.phoenixEndpoint ?? userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT; userContext.features.phoenixEndpoint ?? userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT;
if (!validateEndpoint(phoenixEndpoint, allowedJunoOrigins)) {
const error = `${phoenixEndpoint} not allowed as juno endpoint`; if (!validateEndpoint(toolsEndpoint, allowedJunoOrigins)) {
const error = `${toolsEndpoint} not allowed as tools endpoint`;
console.error(error); console.error(error);
throw new Error(error); throw new Error(error);
} }
return phoenixEndpoint; return `${toolsEndpoint}/api/controlplane/toolscontainer/cosmosaccounts${this.armResourceId}`;
}
public getPhoenixControlPlanePathPrefix(): string {
return `${PhoenixClient.getPhoenixEndpoint()}/api/controlplane/toolscontainer/cosmosaccounts${
userContext.databaseAccount.id
}`;
} }
private static getHeaders(): HeadersInit { private static getHeaders(): HeadersInit {

View File

@ -24,6 +24,10 @@ export class JupyterLabAppFactory {
this.isShellStarted = content?.includes("Connected to") && content?.includes("cqlsh"); this.isShellStarted = content?.includes("Connected to") && content?.includes("cqlsh");
} }
private isPostgresShellStarted(content: string | undefined) {
this.isShellStarted = content?.includes("citus=>");
}
constructor(closeTab: () => void) { constructor(closeTab: () => void) {
this.onShellExited = closeTab; this.onShellExited = closeTab;
this.isShellStarted = false; this.isShellStarted = false;
@ -36,6 +40,9 @@ export class JupyterLabAppFactory {
case "Cassandra": case "Cassandra":
this.checkShellStarted = this.isCassandraShellStarted; this.checkShellStarted = this.isCassandraShellStarted;
break; break;
case "Postgres":
this.checkShellStarted = this.isPostgresShellStarted;
break;
} }
} }