Postgre quickstart UI (#1319)

This commit is contained in:
victor-meng
2022-09-14 14:42:09 -07:00
committed by GitHub
parent d6a58fd45f
commit d54c896d74
29 changed files with 801 additions and 64 deletions

View File

@@ -185,7 +185,9 @@ export default class Explorer {
useNotebook.getState().setNotebookBasePath(userContext.features.notebookBasePath);
}
this.refreshExplorer();
if (!userContext.features.enablePGQuickstart || userContext.apiType !== "Postgres") {
this.refreshExplorer();
}
}
public async initiateAndRefreshNotebookList(): Promise<void> {

View File

@@ -6,6 +6,7 @@
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
import { useNotebook } from "Explorer/Notebook/useNotebook";
import * as React from "react";
import { userContext } from "UserContext";
import create, { UseStore } from "zustand";
import { ConnectionStatusType, StyleConstants } from "../../../Common/Constants";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
@@ -33,6 +34,22 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
const buttons = useCommandBar((state) => state.contextButtons);
const backgroundColor = StyleConstants.BaseLight;
if (userContext.features.enablePGQuickstart && userContext.apiType === "Postgres") {
const buttons = CommandBarComponentButtonFactory.createPostgreButtons(container);
return (
<div className="commandBarContainer">
<FluentCommandBar
ariaLabel="Use left and right arrow keys to navigate between commands"
items={CommandBarUtil.convertButton(buttons, backgroundColor)}
styles={{
root: { backgroundColor: backgroundColor },
}}
overflowButtonProps={{ ariaLabel: "More commands" }}
/>
</div>
);
}
const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(container, selectedNodeState);
const contextButtons = (buttons || []).concat(
CommandBarComponentButtonFactory.createContextCommandBarButtons(container, selectedNodeState)

View File

@@ -585,3 +585,18 @@ function createStaticCommandBarButtonsForResourceToken(
return [newSqlQueryBtn, openQueryBtn];
}
export function createPostgreButtons(container: Explorer): CommandButtonComponentProps[] {
const postgreShellLabel = "Open PostgreSQL Shell";
const openPostgreShellBtn = {
iconSrc: HostedTerminalIcon,
iconAlt: postgreShellLabel,
onCommandClick: () => container.openNotebookTerminal(ViewModels.TerminalKind.Mongo),
commandButtonLabel: postgreShellLabel,
hasPopup: false,
disabled: false,
ariaLabel: postgreShellLabel,
};
return [openPostgreShellBtn];
}

View File

@@ -0,0 +1,290 @@
import {
DefaultButton,
Icon,
IconButton,
Image,
IPivotItemProps,
Pivot,
PivotItem,
PrimaryButton,
Stack,
Text,
TextField,
} from "@fluentui/react";
import React, { useState } from "react";
import Youtube from "react-youtube";
import Pivot1SelectedIcon from "../../../images/Pivot1_selected.svg";
import Pivot2Icon from "../../../images/Pivot2.svg";
import Pivot2SelectedIcon from "../../../images/Pivot2_selected.svg";
import Pivot3Icon from "../../../images/Pivot3.svg";
import Pivot3SelectedIcon from "../../../images/Pivot3_selected.svg";
import Pivot4Icon from "../../../images/Pivot4.svg";
import Pivot4SelectedIcon from "../../../images/Pivot4_selected.svg";
import Pivot5Icon from "../../../images/Pivot5.svg";
import Pivot5SelectedIcon from "../../../images/Pivot5_selected.svg";
import CompleteIcon from "../../../images/QuickstartComplete.svg";
import { ReactTabKind, useTabs } from "../../hooks/useTabs";
enum GuideSteps {
Login,
NewTable,
DistributeTable,
LoadData,
Query,
}
export const QuickstartGuide: React.FC = (): JSX.Element => {
const [currentStep, setCurrentStep] = useState<number>(0);
const newTableCommand = `CREATE TABLE github_users
(
user_id bigint,
url text,
login text,
.....`;
const distributeTableCommand = `SELECT create_distributed_table('github_users', 'user_id');
SELECT create_distributed_table('github_events', 'user_id');`;
const loadDataCommand = `-- download users and store in table
COPY github_users FROM PROGRAM 'curl https://examples.citusdata.com/
users.csv' WITH (FORMAT CSV)`;
const queryCommand = `-- Find all events for a single user.
-- (A common transactional/operational query)
SELECT created_at, event_type, repo->>'name' AS repo_name
FROM github_events
WHERE user_id = 3861633;`;
const onCopyBtnClicked = (selector: string): void => {
const textfield: HTMLInputElement = document.querySelector(selector);
textfield.select();
document.execCommand("copy");
};
const getPivotHeaderIcon = (step: number): string => {
switch (step) {
case 0:
return Pivot1SelectedIcon;
case 1:
return step === currentStep ? Pivot2SelectedIcon : Pivot2Icon;
case 2:
return step === currentStep ? Pivot3SelectedIcon : Pivot3Icon;
case 3:
return step === currentStep ? Pivot4SelectedIcon : Pivot4Icon;
case 4:
return step === currentStep ? Pivot5SelectedIcon : Pivot5Icon;
default:
return "";
}
};
const customPivotHeaderRenderer = (
link: IPivotItemProps,
defaultRenderer: (link?: IPivotItemProps) => JSX.Element | null,
step: number
): JSX.Element | null => {
if (!link || !defaultRenderer) {
return null;
}
return (
<Stack horizontal verticalAlign="center">
{currentStep > step ? (
<Icon iconName="CompletedSolid" style={{ color: "#57A300", marginRight: 8 }} />
) : (
<Image style={{ marginRight: 8 }} src={getPivotHeaderIcon(step)} />
)}
{defaultRenderer({ ...link, itemIcon: undefined })}
</Stack>
);
};
return (
<Stack style={{ paddingTop: 8, height: "100%", width: "100%" }}>
<Stack style={{ flexGrow: 1, padding: "0 20px", overflow: "auto" }}>
<Text variant="xxLarge">Quick start guide</Text>
<Text variant="medium">Gettings started in Cosmos DB</Text>
{currentStep < 5 && (
<Pivot style={{ marginTop: 10, width: "100%" }} selectedKey={GuideSteps[currentStep]}>
<PivotItem
headerText="Login"
onRenderItemLink={(props, defaultRenderer) => customPivotHeaderRenderer(props, defaultRenderer, 0)}
itemKey={GuideSteps[0]}
onClick={() => setCurrentStep(0)}
>
<Stack style={{ marginTop: 20 }}>
<Text>
This tutorial walks you through the most essential Cosmos DB PostgreSQL statements that will be used
in the PostgreSQL shell (on the right). You can also choose to go through this quick start by
connecting to PGAdmin or other tooling of your choice. <br />
<br /> Before you can interact with your data using PGShell, you will need to login - please follow
instructions on the right to enter your password
</Text>
<Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
</Stack>
</PivotItem>
<PivotItem
headerText="New table"
onRenderItemLink={(props, defaultRenderer) => customPivotHeaderRenderer(props, defaultRenderer, 1)}
itemKey={GuideSteps[1]}
onClick={() => setCurrentStep(1)}
>
<Stack style={{ marginTop: 20 }}>
<Text>
After logging in, lets create some new tables for storing data. We will start with two sample tables
- one for storing github users and one for storing github events
</Text>
<DefaultButton style={{ marginTop: 16, width: 110 }}>New table</DefaultButton>
<Stack horizontal style={{ marginTop: 16 }}>
<TextField
id="newTableCommand"
multiline
rows={6}
readOnly
defaultValue={newTableCommand}
styles={{ root: { width: "90%" } }}
/>
<IconButton
iconProps={{
iconName: "Copy",
}}
onClick={() => onCopyBtnClicked("#newTableCommand")}
/>
</Stack>
<Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
</Stack>
</PivotItem>
<PivotItem
headerText="Distribute table"
onRenderItemLink={(props, defaultRenderer) => customPivotHeaderRenderer(props, defaultRenderer, 2)}
itemKey={GuideSteps[2]}
onClick={() => setCurrentStep(2)}
>
<Stack style={{ marginTop: 20 }}>
<Text>
Congratulations, you have now created your first 2 tables.
<br />
<br />
Your table needs to be sharded on the worker nodes. You need to distribute table before you load any
data or run any queries
</Text>
<DefaultButton style={{ marginTop: 16, width: 150 }}>Distribute table</DefaultButton>
<Stack horizontal style={{ marginTop: 16 }}>
<TextField
id="distributeTableCommand"
multiline
rows={2}
readOnly
defaultValue={distributeTableCommand}
styles={{ root: { width: "90%" } }}
/>
<IconButton
iconProps={{
iconName: "Copy",
}}
onClick={() => onCopyBtnClicked("#distributeTableCommand")}
/>
</Stack>
<Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
</Stack>
</PivotItem>
<PivotItem
headerText="Load data"
onRenderItemLink={(props, defaultRenderer) => customPivotHeaderRenderer(props, defaultRenderer, 3)}
itemKey={GuideSteps[3]}
onClick={() => setCurrentStep(3)}
>
<Stack style={{ marginTop: 20 }}>
<Text>
We&apos;re ready to fill the tables with sample data.
<br />
<br />
For this quick start, we&apos;ll use a dataset previously captured from the GitHub API. Run the
command below to load the data
</Text>
<DefaultButton style={{ marginTop: 16, width: 110 }}>Load data</DefaultButton>
<Stack horizontal style={{ marginTop: 16 }}>
<TextField
id="loadDataCommand"
multiline
rows={4}
readOnly
defaultValue={loadDataCommand}
styles={{ root: { width: "90%" } }}
/>
<IconButton
iconProps={{
iconName: "Copy",
}}
onClick={() => onCopyBtnClicked("#loadDataCommand")}
/>
</Stack>
<Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
</Stack>
</PivotItem>
<PivotItem
headerText="Query"
onRenderItemLink={(props, defaultRenderer) => customPivotHeaderRenderer(props, defaultRenderer, 4)}
itemKey={GuideSteps[4]}
onClick={() => setCurrentStep(4)}
>
<Stack style={{ marginTop: 20 }}>
<Text>
github_users is a distributed table, meaning its data is divided between multiple shards. Hyperscale
(Citus) automatically runs the count on all shards in parallel, and combines the results. Lets try a
query.
</Text>
<DefaultButton style={{ marginTop: 16, width: 110 }}>Try query</DefaultButton>
<Stack horizontal style={{ marginTop: 16 }}>
<TextField
id="queryCommand"
multiline
rows={6}
readOnly
defaultValue={queryCommand}
styles={{ root: { width: "90%" } }}
/>
<IconButton
iconProps={{
iconName: "Copy",
}}
onClick={() => onCopyBtnClicked("#queryCommand")}
/>
</Stack>
<Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
</Stack>
</PivotItem>
</Pivot>
)}
{currentStep === 5 && (
<Stack style={{ margin: "auto" }} horizontalAlign="center">
<Image src={CompleteIcon} />
<Text variant="mediumPlus" style={{ fontWeight: 600, marginTop: 7 }}>
You are all set!
</Text>
<Text variant="mediumPlus" style={{ marginTop: 8 }}>
You have completed the quick start guide.{" "}
</Text>
</Stack>
)}
</Stack>
<Stack horizontal style={{ padding: "16px 20px", boxShadow: "inset 0px 1px 0px rgba(204, 204, 204, 0.8)" }}>
<DefaultButton disabled={currentStep === 0} onClick={() => setCurrentStep(currentStep - 1)}>
Previous
</DefaultButton>
{currentStep < 5 && (
<PrimaryButton onClick={() => setCurrentStep(currentStep + 1)} style={{ marginLeft: 8 }}>
Next
</PrimaryButton>
)}
{currentStep === 5 && (
<PrimaryButton
onClick={() => useTabs.getState().closeReactTab(ReactTabKind.Quickstart)}
style={{ marginLeft: 8 }}
>
Close
</PrimaryButton>
)}
</Stack>
</Stack>
);
};

View File

@@ -6,7 +6,7 @@ import { Action } from "Shared/Telemetry/TelemetryConstants";
import { traceCancel, traceSuccess } from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext";
export const QuickstartTutorial: React.FC = (): JSX.Element => {
export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
const { step, isSampleDBExpanded, isDocumentsTabOpened, sampleCollection, setStep } = useTeachingBubble();
const onDimissTeachingBubble = (): void => {

View File

@@ -1,8 +1,19 @@
/**
* Accordion top class
*/
import { Coachmark, DirectionalHint, Image, Link, Stack, TeachingBubbleContent, Text } from "@fluentui/react";
import {
Coachmark,
DirectionalHint,
Image,
Link,
Stack,
TeachingBubble,
TeachingBubbleContent,
Text,
} from "@fluentui/react";
import { TerminalKind } from "Contracts/ViewModels";
import { useCarousel } from "hooks/useCarousel";
import { usePostgres } from "hooks/usePostgres";
import { ReactTabKind, useTabs } from "hooks/useTabs";
import * as React from "react";
import { Action } from "Shared/Telemetry/TelemetryConstants";
@@ -12,6 +23,7 @@ import ContainersIcon from "../../../images/Containers.svg";
import LinkIcon from "../../../images/Link_blue.svg";
import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
import NotebookColorIcon from "../../../images/Notebooks.svg";
import PowerShellIcon from "../../../images/PowerShell.svg";
import QuickStartIcon from "../../../images/Quickstart_Lightning.svg";
import CollectionIcon from "../../../images/tree-collection.svg";
import * as Constants from "../../Common/Constants";
@@ -73,6 +85,12 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
() => this.setState({}),
(state) => state.showCoachMark
),
},
{
dispose: usePostgres.subscribe(
() => this.setState({}),
(state) => state.showPostgreTeachingBubble
),
}
);
}
@@ -91,11 +109,33 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
<div className="splashScreenContainer">
<div className="splashScreen">
<div className="title">
Welcome to Cosmos DB
{userContext.apiType === "Postgres" ? "Welcome to Cosmos DB - PostgreSQL" : "Welcome to Cosmos DB"}
<FeaturePanelLauncher />
</div>
<div className="subtitle">Globally distributed, multi-model database service for any scale</div>
<div className="subtitle">
{userContext.apiType === "Postgres"
? "Get started with our sample datasets, documentation, and additional tools."
: "Globally distributed, multi-model database service for any scale"}
</div>
<div className="mainButtonsContainer">
{userContext.apiType === "Postgres" && usePostgres.getState().showPostgreTeachingBubble && (
<TeachingBubble
headline="New to Cosmos DB PGSQL?"
target={"#quickstartDescription"}
hasCloseButton
onDismiss={() => usePostgres.getState().setShowPostgreTeachingBubble(false)}
primaryButtonProps={{
text: "Get started",
onClick: () => {
useTabs.getState().openAndActivateReactTab(ReactTabKind.Quickstart);
usePostgres.getState().setShowPostgreTeachingBubble(false);
},
}}
>
Welcome! If you are new to Cosmos DB PGSQL and need help with getting started, here is where you can
find sample data, query.
</TeachingBubble>
)}
{mainItems.map((item) => (
<Stack
horizontal
@@ -150,20 +190,49 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
</TeachingBubbleContent>
</Coachmark>
)}
<div className="moreStuffContainer">
<div className="moreStuffColumn commonTasks">
<div className="title">Recents</div>
{this.getRecentItems()}
{userContext.apiType === "Postgres" ? (
<Stack horizontal style={{ margin: "0 auto" }} tokens={{ childrenGap: "15%" }}>
<Stack>
<Text
variant="large"
style={{
marginBottom: 16,
fontFamily: '"Segoe UI Semibold", "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif',
}}
>
Next steps
</Text>
{this.getNextStepItems()}
</Stack>
<Stack>
<Text
variant="large"
style={{
marginBottom: 16,
fontFamily: '"Segoe UI Semibold", "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif',
}}
>
Tips & learn more
</Text>
{this.getTipsAndLearnMoreItems()}
</Stack>
</Stack>
) : (
<div className="moreStuffContainer">
<div className="moreStuffColumn commonTasks">
<div className="title">Recents</div>
{this.getRecentItems()}
</div>
<div className="moreStuffColumn">
<div className="title">Top 3 things you need to know</div>
{this.top3Items()}
</div>
<div className="moreStuffColumn tipsContainer">
<div className="title">Learning Resources</div>
{this.getLearningResourceItems()}
</div>
</div>
<div className="moreStuffColumn">
<div className="title">Top 3 things you need to know</div>
{this.top3Items()}
</div>
<div className="moreStuffColumn tipsContainer">
<div className="title">Learning Resources</div>
{this.getLearningResourceItems()}
</div>
</div>
)}
</div>
</div>
</form>
@@ -184,14 +253,16 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
public createMainItems(): SplashScreenItem[] {
const heroes: SplashScreenItem[] = [];
if (userContext.apiType === "SQL" || userContext.apiType === "Mongo") {
if (userContext.apiType === "SQL" || userContext.apiType === "Mongo" || userContext.apiType === "Postgres") {
const launchQuickstartBtn = {
id: "quickstartDescription",
iconSrc: QuickStartIcon,
title: "Launch quick start",
description: "Launch a quick start tutorial to get started with sample data",
onClick: () => {
this.container.onNewCollectionClicked({ isQuickstart: true });
userContext.apiType === "Postgres"
? useTabs.getState().openAndActivateReactTab(ReactTabKind.Quickstart)
: this.container.onNewCollectionClicked({ isQuickstart: true });
traceOpen(Action.LaunchQuickstart, { apiType: userContext.apiType });
},
};
@@ -206,21 +277,34 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
heroes.push(newNotebookBtn);
}
const newContainerBtn = {
iconSrc: ContainersIcon,
title: `New ${getCollectionName()}`,
description: "Create a new container for storage and throughput",
onClick: () => {
this.container.onNewCollectionClicked();
traceOpen(Action.NewContainerHomepage, { apiType: userContext.apiType });
},
};
heroes.push(newContainerBtn);
if (userContext.apiType === "Postgres") {
const postgreShellBtn = {
iconSrc: PowerShellIcon,
title: "PostgreSQL Shell",
description: "Create table and interact with data using PostgreSQLs shell interface",
onClick: () => this.container.openNotebookTerminal(TerminalKind.Mongo),
};
heroes.push(postgreShellBtn);
} else {
const newContainerBtn = {
iconSrc: ContainersIcon,
title: `New ${getCollectionName()}`,
description: "Create a new container for storage and throughput",
onClick: () => {
this.container.onNewCollectionClicked();
traceOpen(Action.NewContainerHomepage, { apiType: userContext.apiType });
},
};
heroes.push(newContainerBtn);
}
const connectBtn = {
iconSrc: ConnectIcon,
title: "Connect",
description: "Prefer using your own choice of tooling? Find the connection string you need to connect",
title: userContext.apiType === "Postgres" ? "Connect with PG Admin" : "Connect",
description:
userContext.apiType === "Postgres"
? "Prefer using your own choice of tooling? Find the connection string you need to connect"
: "Prefer PGadmin? Find your connection strings here",
onClick: () => useTabs.getState().openAndActivateReactTab(ReactTabKind.Connect),
};
heroes.push(connectBtn);
@@ -280,6 +364,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
let items: { link: string; title: string; description: string }[];
switch (userContext.apiType) {
case "SQL":
case "Postgres":
items = [
{
link: "https://aka.ms/msl-modeling-partitioning-2",
@@ -426,6 +511,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
let items: { link: string; title: string; description: string }[];
switch (userContext.apiType) {
case "SQL":
case "Postgres":
items = [
{
link: "https://aka.ms/msl-sdk-connect",
@@ -544,4 +630,76 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
</Stack>
);
}
private getNextStepItems(): JSX.Element {
const items: { link: string; title: string; description: string }[] = [
{
link: "",
title: "Performance tuning",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
{
link: "",
title: "Join Citus community",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
{
link: "",
title: "Useful diagnostic queries",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
];
return (
<Stack>
{items.map((item, i) => (
<Stack key={`nextStep${i}`} style={{ marginBottom: 26 }}>
<Stack horizontal verticalAlign="center" style={{ fontSize: 14 }}>
<Link href={item.link} target="_blank" style={{ marginRight: 5 }}>
{item.title}
</Link>
<Image src={LinkIcon} />
</Stack>
<Text>{item.description}</Text>
</Stack>
))}
</Stack>
);
}
private getTipsAndLearnMoreItems(): JSX.Element {
const items: { link: string; title: string; description: string }[] = [
{
link: "",
title: "Data modeling",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
{
link: "",
title: "How to choose a distribution Column",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
{
link: "",
title: "Build apps with Python/ Java/ Django",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
];
return (
<Stack>
{items.map((item, i) => (
<Stack key={`tips${i}`} style={{ marginBottom: 26 }}>
<Stack horizontal verticalAlign="center" style={{ fontSize: 14 }}>
<Link href={item.link} target="_blank" style={{ marginRight: 5 }}>
{item.title}
</Link>
<Image src={LinkIcon} />
</Stack>
<Text>{item.description}</Text>
</Stack>
))}
</Stack>
);
}
}

View File

@@ -0,0 +1,44 @@
import { Spinner, SpinnerSize, Stack } from "@fluentui/react";
import { NotebookWorkspaceConnectionInfo } from "Contracts/DataModels";
import { NotebookTerminalComponent } from "Explorer/Controls/Notebook/NotebookTerminalComponent";
import Explorer from "Explorer/Explorer";
import { useNotebook } from "Explorer/Notebook/useNotebook";
import { QuickstartGuide } from "Explorer/Quickstart/QuickstartGuide";
import React, { useEffect } from "react";
import { userContext } from "UserContext";
interface QuickstartTabProps {
explorer: Explorer;
}
export const QuickstartTab: React.FC<QuickstartTabProps> = ({ explorer }: QuickstartTabProps): JSX.Element => {
const notebookServerInfo = useNotebook((state) => state.notebookServerInfo);
useEffect(() => {
explorer.allocateContainer();
}, []);
const getNotebookServerInfo = (): NotebookWorkspaceConnectionInfo => ({
authToken: notebookServerInfo.authToken,
notebookServerEndpoint: `${notebookServerInfo.notebookServerEndpoint?.replace(/\/+$/, "")}/mongo`,
forwardingId: notebookServerInfo.forwardingId,
});
return (
<Stack style={{ width: "100%" }} horizontal>
<Stack style={{ width: "50%" }}>
<QuickstartGuide />
</Stack>
<Stack style={{ width: "50%", borderLeft: "black solid 1px" }}>
{notebookServerInfo?.notebookServerEndpoint && (
<NotebookTerminalComponent
notebookServerInfo={getNotebookServerInfo()}
databaseAccount={userContext.databaseAccount}
tabId="EmbbedTerminal"
/>
)}
{!notebookServerInfo?.notebookServerEndpoint && (
<Spinner styles={{ root: { marginTop: 10 } }} size={SpinnerSize.large}></Spinner>
)}
</Stack>
</Stack>
);
};

View File

@@ -2,6 +2,7 @@ import { CollectionTabKind } from "Contracts/ViewModels";
import Explorer from "Explorer/Explorer";
import { SplashScreen } from "Explorer/SplashScreen/SplashScreen";
import { ConnectTab } from "Explorer/Tabs/ConnectTab";
import { QuickstartTab } from "Explorer/Tabs/QuickstartTab";
import { useTeachingBubble } from "hooks/useTeachingBubble";
import ko from "knockout";
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
@@ -191,6 +192,8 @@ const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): J
return <ConnectTab />;
case ReactTabKind.Home:
return <SplashScreen explorer={explorer} />;
case ReactTabKind.Quickstart:
return <QuickstartTab explorer={explorer} />;
default:
throw Error(`Unsupported tab kind ${ReactTabKind[activeReactTab]}`);
}

View File

@@ -1,12 +1,13 @@
// CSS Dependencies
import { initializeIcons } from "@fluentui/react";
import "bootstrap/dist/css/bootstrap.css";
import { MongoQuickstartTutorial } from "Explorer/Tutorials/MongoQuickstartTutorial";
import { QuickstartCarousel } from "Explorer/Tutorials/QuickstartCarousel";
import { QuickstartTutorial } from "Explorer/Tutorials/QuickstartTutorial";
import { QuickstartCarousel } from "Explorer/Quickstart/QuickstartCarousel";
import { MongoQuickstartTutorial } from "Explorer/Quickstart/Tutorials/MongoQuickstartTutorial";
import { SQLQuickstartTutorial } from "Explorer/Quickstart/Tutorials/SQLQuickstartTutorial";
import { useCarousel } from "hooks/useCarousel";
import React, { useState } from "react";
import ReactDOM from "react-dom";
import { userContext } from "UserContext";
import "../externals/jquery-ui.min.css";
import "../externals/jquery-ui.min.js";
import "../externals/jquery-ui.structure.min.css";
@@ -84,23 +85,25 @@ const App: React.FunctionComponent = () => {
{/* Collections Tree and Tabs - Begin */}
<div className="resourceTreeAndTabs">
{/* Collections Tree - Start */}
<div id="resourcetree" data-test="resourceTreeId" className="resourceTree">
<div className="collectionsTreeWithSplitter">
{/* Collections Tree Expanded - Start */}
<ResourceTreeContainer
container={explorer}
toggleLeftPaneExpanded={toggleLeftPaneExpanded}
isLeftPaneExpanded={isLeftPaneExpanded}
/>
{/* Collections Tree Expanded - End */}
{/* Collections Tree Collapsed - Start */}
<CollapsedResourceTree
toggleLeftPaneExpanded={toggleLeftPaneExpanded}
isLeftPaneExpanded={isLeftPaneExpanded}
/>
{/* Collections Tree Collapsed - End */}
{userContext.apiType !== "Postgres" && (
<div id="resourcetree" data-test="resourceTreeId" className="resourceTree">
<div className="collectionsTreeWithSplitter">
{/* Collections Tree Expanded - Start */}
<ResourceTreeContainer
container={explorer}
toggleLeftPaneExpanded={toggleLeftPaneExpanded}
isLeftPaneExpanded={isLeftPaneExpanded}
/>
{/* Collections Tree Expanded - End */}
{/* Collections Tree Collapsed - Start */}
<CollapsedResourceTree
toggleLeftPaneExpanded={toggleLeftPaneExpanded}
isLeftPaneExpanded={isLeftPaneExpanded}
/>
{/* Collections Tree Collapsed - End */}
</div>
</div>
</div>
)}
<Tabs explorer={explorer} />
</div>
{/* Collections Tree and Tabs - End */}
@@ -116,7 +119,7 @@ const App: React.FunctionComponent = () => {
<SidePanel />
<Dialog />
{<QuickstartCarousel isOpen={isCarouselOpen} />}
{<QuickstartTutorial />}
{<SQLQuickstartTutorial />}
{<MongoQuickstartTutorial />}
</div>
);

View File

@@ -29,6 +29,7 @@ export type Features = {
readonly mongoProxyEndpoint?: string;
readonly mongoProxyAPIs?: string;
readonly enableThroughputCap: boolean;
readonly enablePGQuickstart: boolean;
// can be set via both flight and feature flag
autoscaleDefault: boolean;
@@ -90,6 +91,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
partitionKeyDefault2: "true" === get("pkpartitionkeytest"),
notebooksDownBanner: "true" === get("notebooksDownBanner"),
enableThroughputCap: "true" === get("enablethroughputcap"),
enablePGQuickstart: "true" === get("enablepgquickstart"),
};
}

View File

@@ -1,4 +1,5 @@
import { useCarousel } from "hooks/useCarousel";
import { usePostgres } from "hooks/usePostgres";
import { Action } from "Shared/Telemetry/TelemetryConstants";
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
import { AuthType } from "./AuthType";
@@ -54,7 +55,7 @@ interface UserContext {
collectionCreationDefaults: CollectionCreationDefaults;
}
export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra";
export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres";
export type PortalEnv = "localhost" | "blackforest" | "fairfax" | "mooncake" | "prod" | "dev";
const ONE_WEEK_IN_MS = 604800000;
@@ -92,13 +93,15 @@ function updateUserContext(newContext: Partial<UserContext>): void {
ONE_WEEK_IN_MS
);
if (
!localStorage.getItem(newContext.databaseAccount.id) &&
(userContext.isTryCosmosDBSubscription || isNewAccount)
) {
useCarousel.getState().setShouldOpen(true);
localStorage.setItem(newContext.databaseAccount.id, "true");
traceOpen(Action.OpenCarousel);
if (!localStorage.getItem(newContext.databaseAccount.id)) {
if (newContext.apiType === "Postgres") {
usePostgres.getState().setShowPostgreTeachingBubble(true);
localStorage.setItem(newContext.databaseAccount.id, "true");
} else if (userContext.isTryCosmosDBSubscription || isNewAccount) {
useCarousel.getState().setShouldOpen(true);
localStorage.setItem(newContext.databaseAccount.id, "true");
traceOpen(Action.OpenCarousel);
}
}
}
Object.assign(userContext, newContext);
@@ -108,6 +111,11 @@ function apiType(account: DatabaseAccount | undefined): ApiType {
if (!account) {
return "SQL";
}
if (features.enablePGQuickstart) {
return "Postgres";
}
const capabilities = account.properties?.capabilities;
if (capabilities) {
if (capabilities.find((c) => c.name === "EnableCassandra")) {

View File

@@ -17,6 +17,8 @@ export const getCollectionName = (isPlural?: boolean): string => {
case "Gremlin":
collectionName = "Graph";
break;
case "Postgres":
return "";
default:
unknownApiType = userContext.apiType;
throw new Error(`Unknown API type: ${unknownApiType}`);

11
src/hooks/usePostgres.ts Normal file
View File

@@ -0,0 +1,11 @@
import create, { UseStore } from "zustand";
interface TeachingBubbleState {
showPostgreTeachingBubble: boolean;
setShowPostgreTeachingBubble: (showPostgreTeachingBubble: boolean) => void;
}
export const usePostgres: UseStore<TeachingBubbleState> = create((set) => ({
showPostgreTeachingBubble: false,
setShowPostgreTeachingBubble: (showPostgreTeachingBubble: boolean) => set({ showPostgreTeachingBubble }),
}));

View File

@@ -7,8 +7,8 @@ import TabsBase from "../Explorer/Tabs/TabsBase";
interface TabsState {
openedTabs: TabsBase[];
openedReactTabs: ReactTabKind[];
activeTab: TabsBase;
activeReactTab: ReactTabKind;
activeTab: TabsBase | undefined;
activeReactTab: ReactTabKind | undefined;
activateTab: (tab: TabsBase) => void;
activateNewTab: (tab: TabsBase) => void;
activateReactTab: (tabkind: ReactTabKind) => void;
@@ -25,6 +25,7 @@ interface TabsState {
export enum ReactTabKind {
Connect,
Home,
Quickstart,
}
export const useTabs: UseStore<TabsState> = create((set, get) => ({