diff --git a/src/Explorer/ContextMenuButtonFactory.tsx b/src/Explorer/ContextMenuButtonFactory.tsx index 70d02e5aa..8f85ddbdb 100644 --- a/src/Explorer/ContextMenuButtonFactory.tsx +++ b/src/Explorer/ContextMenuButtonFactory.tsx @@ -39,7 +39,7 @@ export const createDatabaseContextMenu = (container: Explorer, databaseId: strin const items: TreeNodeMenuItem[] = [ { iconSrc: AddCollectionIcon, - onClick: () => container.onNewCollectionClicked(databaseId), + onClick: () => container.onNewCollectionClicked({ databaseId }), label: `New ${getCollectionName()}`, }, ]; diff --git a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx index 54078f936..7ee64287d 100644 --- a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx +++ b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx @@ -16,6 +16,7 @@ export interface ThroughputInputProps { isSharded: boolean; isFreeTier: boolean; showFreeTierExceedThroughputTooltip: boolean; + isQuickstart?: boolean; setThroughputValue: (throughput: number) => void; setIsAutoscale: (isAutoscale: boolean) => void; setIsThroughputCapExceeded: (isThroughputCapExceeded: boolean) => void; @@ -226,6 +227,7 @@ export const ThroughputInput: FunctionComponent = ({ { + public async populateContainerAsync(collection: ViewModels.Collection): Promise { if (!collection) { throw new Error("No container to populate"); } - const promises: Q.Promise[] = []; if (userContext.apiType === "Gremlin") { // For Gremlin, all queries are executed sequentially, because some queries might be dependent on other queries diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index e5eeca0d4..849429c25 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -1131,7 +1131,13 @@ export default class Explorer { } } - public async onNewCollectionClicked(databaseId?: string): Promise { + public async onNewCollectionClicked( + options: { + databaseId?: string; + isQuickstart?: boolean; + showTeachingBubble?: boolean; + } = {} + ): Promise { if (userContext.apiType === "Cassandra") { useSidePanel .getState() @@ -1146,7 +1152,7 @@ export default class Explorer { : await useDatabases.getState().loadDatabaseOffers(); useSidePanel .getState() - .openSidePanel("New " + getCollectionName(), ); + .openSidePanel("New " + getCollectionName(), ); } } diff --git a/src/Explorer/Panes/AddCollectionPanel.test.tsx b/src/Explorer/Panes/AddCollectionPanel.test.tsx new file mode 100644 index 000000000..0e87c61de --- /dev/null +++ b/src/Explorer/Panes/AddCollectionPanel.test.tsx @@ -0,0 +1,15 @@ +import { shallow } from "enzyme"; +import React from "react"; +import Explorer from "../Explorer"; +import { AddCollectionPanel } from "./AddCollectionPanel"; + +const props = { + explorer: new Explorer(), +}; + +describe("AddCollectionPanel", () => { + it("should render Default properly", () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/src/Explorer/Panes/AddCollectionPanel.tsx b/src/Explorer/Panes/AddCollectionPanel.tsx index 05b603f5a..9f279df51 100644 --- a/src/Explorer/Panes/AddCollectionPanel.tsx +++ b/src/Explorer/Panes/AddCollectionPanel.tsx @@ -10,6 +10,7 @@ import { Link, Separator, Stack, + TeachingBubble, Text, TooltipHost, } from "@fluentui/react"; @@ -30,6 +31,7 @@ import { isCapabilityEnabled, isServerlessAccount } from "Utils/CapabilityUtils" import { getUpsellMessage } from "Utils/PricingUtils"; import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent"; import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput"; +import { ContainerSampleGenerator } from "../DataSamples/ContainerSampleGenerator"; import Explorer from "../Explorer"; import { useDatabases } from "../useDatabases"; import { PanelFooterComponent } from "./PanelFooterComponent"; @@ -39,6 +41,8 @@ import { PanelLoadingScreen } from "./PanelLoadingScreen"; export interface AddCollectionPanelProps { explorer: Explorer; databaseId?: string; + isQuickstart?: boolean; + showTeachingBubble?: boolean; } const SharedDatabaseDefault: DataModels.IndexingPolicy = { @@ -93,6 +97,7 @@ export interface AddCollectionPanelState { showErrorDetails: boolean; isExecuting: boolean; isThroughputCapExceeded: boolean; + teachingBubbleStep: number; } export class AddCollectionPanel extends React.Component { @@ -107,11 +112,11 @@ export class AddCollectionPanel extends React.Component )} + {this.state.teachingBubbleStep === 1 && ( + this.setState({ teachingBubbleStep: 2 }) }} + secondaryButtonProps={{ text: "Cancel", onClick: () => this.setState({ teachingBubbleStep: 0 }) }} + onDismiss={() => this.setState({ teachingBubbleStep: 0 })} + footerContent="Step 1 of 4" + > + Database is the parent of a container, create a new database / use an existing one + + )} + + {this.state.teachingBubbleStep === 2 && ( + this.setState({ teachingBubbleStep: 3 }) }} + secondaryButtonProps={{ text: "Previous", onClick: () => this.setState({ teachingBubbleStep: 1 }) }} + onDismiss={() => this.setState({ teachingBubbleStep: 0 })} + footerContent="Step 2 of 4" + > + Cosmos DB recommends sharing throughput across database. Autoscale will give you a flexible amount of + throughput based on the max RU/s set + + )} + + {this.state.teachingBubbleStep === 3 && ( + this.setState({ teachingBubbleStep: 4 }) }} + secondaryButtonProps={{ text: "Previous", onClick: () => this.setState({ teachingBubbleStep: 2 }) }} + onDismiss={() => this.setState({ teachingBubbleStep: 0 })} + footerContent="Step 3 of 4" + > + Name your container + + )} + + {this.state.teachingBubbleStep === 4 && ( + { + this.setState({ teachingBubbleStep: 0 }); + this.submit(); + }, + }} + secondaryButtonProps={{ text: "Previous", onClick: () => this.setState({ teachingBubbleStep: 2 }) }} + onDismiss={() => this.setState({ teachingBubbleStep: 0 })} + footerContent="Step 4 of 4" + > + Last step - you will need to define a partition key for your collection. /address was chosen for this + particular example. A good partition key should have a wide range of possible value + + )} +
{item.title}
-
+
{item.description}
))}
+ {this.state.showCoachmark && ( + + { + this.setState({ showCoachmark: false }); + this.container.onNewCollectionClicked({ isQuickstart: true, showTeachingBubble: true }); + }, + }} + secondaryButtonProps={{ text: "Cancel", onClick: () => this.setState({ showCoachmark: false }) }} + onDismiss={() => this.setState({ showCoachmark: false })} + > + You will be guided to create a sample container with sample data, then we will give you a tour of + data explorer You can also cancel launching this tour and explore yourself + + + )}
@@ -165,11 +202,11 @@ export class SplashScreen extends React.Component { if (userContext.features.enableNewQuickstart) { const launchQuickstartBtn = { + id: "quickstartDescription", iconSrc: QuickStartIcon, title: "Launch quick start", description: "Launch a quick start tutorial to get started with sample data", - // TODO: replace onClick function - onClick: () => 1, + onClick: () => this.container.onNewCollectionClicked({ isQuickstart: true }), }; const newContainerBtn = { diff --git a/test/notebooks/upload.spec.ts b/test/notebooks/upload.spec.ts index 5f2483531..65beeec75 100644 --- a/test/notebooks/upload.spec.ts +++ b/test/notebooks/upload.spec.ts @@ -11,7 +11,7 @@ const fileToUpload = `GettingStarted-ignore${Math.floor(Math.random() * 100000)} fs.copyFileSync(path.join(__dirname, filename), path.join(__dirname, fileToUpload)); test("Notebooks", async () => { - await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner"); + await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner-west-us"); const explorer = await waitForExplorer(); // Upload and Delete Notebook await explorer.click('[data-test="My Notebooks"] [aria-label="More"]'); diff --git a/test/sql/container.spec.ts b/test/sql/container.spec.ts index 4deb3e30e..82fda76aa 100644 --- a/test/sql/container.spec.ts +++ b/test/sql/container.spec.ts @@ -9,7 +9,7 @@ test("SQL CRUD", async () => { const containerId = generateUniqueName("container"); page.setDefaultTimeout(50000); - await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner"); + await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner-west-us"); const explorer = await waitForExplorer(); await explorer.click('[data-test="New Container"]'); await explorer.fill('[aria-label="New database id"]', databaseId); diff --git a/test/sql/resourceToken.spec.ts b/test/sql/resourceToken.spec.ts index bcaf45ad5..66a8cd5df 100644 --- a/test/sql/resourceToken.spec.ts +++ b/test/sql/resourceToken.spec.ts @@ -15,8 +15,8 @@ const resourceGroupName = "runners"; test("Resource token", async () => { const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId); const armClient = new CosmosDBManagementClient(credentials, subscriptionId); - const account = await armClient.databaseAccounts.get(resourceGroupName, "portal-sql-runner"); - const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, "portal-sql-runner"); + const account = await armClient.databaseAccounts.get(resourceGroupName, "portal-sql-runner-west-us"); + const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, "portal-sql-runner-west-us"); const dbId = generateUniqueName("db"); const collectionId = generateUniqueName("col"); const client = new CosmosClient({ diff --git a/test/testExplorer/TestExplorer.ts b/test/testExplorer/TestExplorer.ts index a83484ebb..06d810da2 100644 --- a/test/testExplorer/TestExplorer.ts +++ b/test/testExplorer/TestExplorer.ts @@ -8,7 +8,7 @@ import { get, listKeys } from "../../src/Utils/arm/generatedClients/cosmos/datab const resourceGroup = process.env.RESOURCE_GROUP || ""; const subscriptionId = process.env.SUBSCRIPTION_ID || ""; const urlSearchParams = new URLSearchParams(window.location.search); -const accountName = urlSearchParams.get("accountName") || "portal-sql-runner"; +const accountName = urlSearchParams.get("accountName") || "portal-sql-runner-west-us"; const selfServeType = urlSearchParams.get("selfServeType") || "example"; const iframeSrc = urlSearchParams.get("iframeSrc") || "explorer.html?platform=Portal&disablePortalInitCache";