mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-18 08:21:37 +00:00
Postgre quickstart UI (#1319)
This commit is contained in:
@@ -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> {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
290
src/Explorer/Quickstart/QuickstartGuide.tsx
Normal file
290
src/Explorer/Quickstart/QuickstartGuide.tsx
Normal 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, let’s 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're ready to fill the tables with sample data.
|
||||
<br />
|
||||
<br />
|
||||
For this quick start, we'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. Let’s 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>
|
||||
);
|
||||
};
|
||||
@@ -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 => {
|
||||
@@ -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 PostgreSQL’s 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
44
src/Explorer/Tabs/QuickstartTab.tsx
Normal file
44
src/Explorer/Tabs/QuickstartTab.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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]}`);
|
||||
}
|
||||
|
||||
43
src/Main.tsx
43
src/Main.tsx
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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
11
src/hooks/usePostgres.ts
Normal 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 }),
|
||||
}));
|
||||
@@ -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) => ({
|
||||
|
||||
Reference in New Issue
Block a user