diff --git a/src/Explorer/Controls/TreeComponent/TreeComponent.tsx b/src/Explorer/Controls/TreeComponent/TreeComponent.tsx index c54d8fcf4..83b00b362 100644 --- a/src/Explorer/Controls/TreeComponent/TreeComponent.tsx +++ b/src/Explorer/Controls/TreeComponent/TreeComponent.tsx @@ -173,6 +173,7 @@ export class TreeNodeComponent extends React.Component) => this.onNodeClick(event, node)} onKeyPress={(event: React.KeyboardEvent) => this.onNodeKeyPress(event, node)} role="treeitem" + id={node.id} >
{ if (userContext.apiType === "Cassandra") { diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index c4fa1b79c..3a1a9c9db 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -309,6 +309,7 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") { const label = "New SQL Query"; return { + id: "newQueryBtn", iconSrc: AddSqlQueryIcon, iconAlt: label, onCommandClick: () => { @@ -323,6 +324,7 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB } else if (userContext.apiType === "Mongo") { const label = "New Query"; return { + id: "newQueryBtn", iconSrc: AddSqlQueryIcon, iconAlt: label, onCommandClick: () => { @@ -409,6 +411,7 @@ function applyNotebooksTemporarilyDownStyle(buttonProps: CommandButtonComponentP function createNewNotebookButton(container: Explorer): CommandButtonComponentProps { const label = "New Notebook"; return { + id: "newNotebookBtn", iconSrc: NewNotebookIcon, iconAlt: label, onCommandClick: () => container.onNewNotebookClicked(), diff --git a/src/Explorer/Panes/AddCollectionPanel.tsx b/src/Explorer/Panes/AddCollectionPanel.tsx index 9f279df51..6f7d51c4b 100644 --- a/src/Explorer/Panes/AddCollectionPanel.tsx +++ b/src/Explorer/Panes/AddCollectionPanel.tsx @@ -8,6 +8,7 @@ import { IconButton, IDropdownOption, Link, + ProgressIndicator, Separator, Stack, TeachingBubble, @@ -21,6 +22,7 @@ import { configContext, Platform } from "ConfigContext"; import * as DataModels from "Contracts/DataModels"; import { SubscriptionType } from "Contracts/SubscriptionType"; import { useSidePanel } from "hooks/useSidePanel"; +import { useTeachingBubble } from "hooks/useTeachingBubble"; import React from "react"; import { CollectionCreation } from "Shared/Constants"; import { Action } from "Shared/Telemetry/TelemetryConstants"; @@ -42,7 +44,6 @@ export interface AddCollectionPanelProps { explorer: Explorer; databaseId?: string; isQuickstart?: boolean; - showTeachingBubble?: boolean; } const SharedDatabaseDefault: DataModels.IndexingPolicy = { @@ -134,7 +135,7 @@ export class AddCollectionPanel extends React.Component { - this.setState({ teachingBubbleStep: 0 }); + this.setState({ teachingBubbleStep: 5 }); this.submit(); }, }} @@ -764,7 +765,30 @@ export class AddCollectionPanel extends React.Component - {this.state.isExecuting && } + {this.state.isExecuting && ( +
+ + {this.state.teachingBubbleStep === 5 && ( + this.setState({ teachingBubbleStep: 0 })} + footerContent={ + + } + > + A sample container is now being created and we are adding sample data for you. It should take about 1 + minute. +
+
+ Once the sample container is created, review your sample dataset and follow next steps +
+ )} +
+ )} ); } @@ -1176,10 +1200,16 @@ export class AddCollectionPanel extends React.Component ( -
+
); diff --git a/src/Explorer/SplashScreen/SplashScreen.tsx b/src/Explorer/SplashScreen/SplashScreen.tsx index 0431d2af8..153c2dd1f 100644 --- a/src/Explorer/SplashScreen/SplashScreen.tsx +++ b/src/Explorer/SplashScreen/SplashScreen.tsx @@ -149,7 +149,7 @@ export class SplashScreen extends React.Component { this.setState({ showCoachmark: false }); - this.container.onNewCollectionClicked({ isQuickstart: true, showTeachingBubble: true }); + this.container.onNewCollectionClicked({ isQuickstart: true }); }, }} secondaryButtonProps={{ text: "Cancel", onClick: () => this.setState({ showCoachmark: false }) }} diff --git a/src/Explorer/Tabs/DocumentsTab.ts b/src/Explorer/Tabs/DocumentsTab.ts index 17538b624..fd51273d7 100644 --- a/src/Explorer/Tabs/DocumentsTab.ts +++ b/src/Explorer/Tabs/DocumentsTab.ts @@ -909,6 +909,7 @@ export default class DocumentsTab extends TabsBase { public static _createUploadButton(container: Explorer): CommandButtonComponentProps { const label = "Upload Item"; return { + id: "uploadItemBtn", iconSrc: UploadIcon, iconAlt: label, onCommandClick: () => { diff --git a/src/Explorer/Tabs/Tabs.tsx b/src/Explorer/Tabs/Tabs.tsx index 7948e871a..ba2493540 100644 --- a/src/Explorer/Tabs/Tabs.tsx +++ b/src/Explorer/Tabs/Tabs.tsx @@ -1,3 +1,5 @@ +import { CollectionTabKind } from "Contracts/ViewModels"; +import { useTeachingBubble } from "hooks/useTeachingBubble"; import ko from "knockout"; import React, { MutableRefObject, useEffect, useRef, useState } from "react"; import loadingIcon from "../../../images/circular_loader_black_16x16.gif"; @@ -113,6 +115,14 @@ function TabPane({ tab, active }: { tab: Tab; active: boolean }) { }; useEffect((): (() => void) | void => { + if ( + tab.tabKind === CollectionTabKind.Documents && + tab.collection?.databaseId === "SampleDB" && + tab.collection?.id() === "SampleContainer" + ) { + useTeachingBubble.getState().setIsDocumentsTabOpened(true); + } + const { current: element } = ref; if (element) { ko.applyBindings(tab, element); diff --git a/src/Explorer/Tree/ResourceTree.tsx b/src/Explorer/Tree/ResourceTree.tsx index 77f694598..4a4ab526c 100644 --- a/src/Explorer/Tree/ResourceTree.tsx +++ b/src/Explorer/Tree/ResourceTree.tsx @@ -1,4 +1,5 @@ import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react"; +import { useTeachingBubble } from "hooks/useTeachingBubble"; import * as React from "react"; import shallow from "zustand/shallow"; import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg"; @@ -436,7 +437,7 @@ export const ResourceTree: React.FC = ({ container }: Resourc const databaseNode: TreeNode = { label: database.id(), iconSrc: CosmosDBIcon, - isExpanded: false, + isExpanded: database.isDatabaseExpanded(), className: "databaseHeader", children: [], isSelected: () => useSelectedNode.getState().isDataNodeSelected(database.id()), @@ -461,6 +462,7 @@ export const ResourceTree: React.FC = ({ container }: Resourc if (database.isDatabaseShared()) { databaseNode.children.push({ + id: database.id() === "SampleDB" ? "sampleScaleSettings" : "", label: "Scale", isSelected: () => useSelectedNode @@ -497,6 +499,7 @@ export const ResourceTree: React.FC = ({ container }: Resourc const children: TreeNode[] = []; children.push({ label: collection.getLabel(), + id: collection.databaseId === "SampleDB" && collection.id() === "SampleContainer" ? "sampleItems" : "", onClick: () => { collection.openTab(); // push to most recent @@ -530,6 +533,10 @@ export const ResourceTree: React.FC = ({ container }: Resourc if (userContext.apiType !== "Cassandra" || !isServerlessAccount()) { children.push({ + id: + collection.databaseId === "SampleDB" && collection.id() === "SampleContainer" && !database.isDatabaseShared() + ? "sampleScaleSettings" + : "", label: database.isDatabaseShared() || isServerlessAccount() ? "Settings" : "Scale & Settings", onClick: collection.onSettingsClick.bind(collection), isSelected: () => @@ -572,7 +579,7 @@ export const ResourceTree: React.FC = ({ container }: Resourc return { label: collection.id(), iconSrc: CollectionIcon, - isExpanded: false, + isExpanded: collection.isCollectionExpanded(), children: children, className: "collectionHeader", contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(container, collection), @@ -586,6 +593,10 @@ export const ResourceTree: React.FC = ({ container }: Resourc ); }, onExpanded: () => { + // TODO: For testing purpose only, remove after + if (collection.databaseId === "SampleDB" && collection.id() === "SampleContainer") { + useTeachingBubble.getState().setIsSampleDBExpanded(true); + } if (showScriptNodes) { collection.loadStoredProcedures(); collection.loadUserDefinedFunctions(); diff --git a/src/Explorer/Tutorials/QuickstartTutorial.tsx b/src/Explorer/Tutorials/QuickstartTutorial.tsx new file mode 100644 index 000000000..9da43060c --- /dev/null +++ b/src/Explorer/Tutorials/QuickstartTutorial.tsx @@ -0,0 +1,164 @@ +import { TeachingBubble } from "@fluentui/react"; +import { useDatabases } from "Explorer/useDatabases"; +import { useTeachingBubble } from "hooks/useTeachingBubble"; +import React from "react"; +import { userContext } from "UserContext"; + +export const QuickstartTutorial: React.FC = (): JSX.Element => { + const { step, isSampleDBExpanded, isDocumentsTabOpened, setStep } = useTeachingBubble(); + + if (!userContext.features.enableNewQuickstart) { + return <>; + } + + switch (step) { + case 1: + return isSampleDBExpanded ? ( + { + const sampleContainer = useDatabases.getState().findCollection("SampleDB", "SampleContainer"); + sampleContainer.openTab(); + setStep(2); + }, + }} + onDismiss={() => setStep(0)} + footerContent="Step 1 of 7" + > + Start viewing and working with your data by opening Items under Data + + ) : ( + <> + ); + case 2: + return isDocumentsTabOpened ? ( + setStep(3), + }} + secondaryButtonProps={{ + text: "Previous", + onClick: () => setStep(1), + }} + onDismiss={() => setStep(0)} + footerContent="Step 2 of 7" + > + View item here using the items window. Additionally you can also filter items to be reviewed with the filter + function + + ) : ( + <> + ); + case 3: + return ( + setStep(4), + }} + secondaryButtonProps={{ + text: "Previous", + onClick: () => setStep(2), + }} + onDismiss={() => setStep(0)} + footerContent="Step 3 of 7" + > + Add new item by copy / pasting jsons; or uploading a json + + ); + case 4: + return ( + setStep(5), + }} + secondaryButtonProps={{ + text: "Previous", + onClick: () => setStep(3), + }} + onDismiss={() => setStep(0)} + footerContent="Step 4 of 7" + > + Query your data using either the filter function or new query. + + ); + case 5: + return ( + setStep(6), + }} + secondaryButtonProps={{ + text: "Previous", + onClick: () => setStep(4), + }} + onDismiss={() => setStep(0)} + footerContent="Step 5 of 7" + > + Change throughput provisioned to your container according to your needs + + ); + case 6: + return ( + setStep(7), + }} + secondaryButtonProps={{ + text: "Previous", + onClick: () => setStep(5), + }} + onDismiss={() => setStep(0)} + footerContent="Step 6 of 7" + > + Visualize your data, store queries in an interactive document + + ); + case 7: + return ( + setStep(7), + }} + secondaryButtonProps={{ + text: "Previous", + onClick: () => setStep(6), + }} + onDismiss={() => setStep(0)} + footerContent="Step 7 of 7" + > + You have finished the tour in data explorer. For next steps, you may want to launch connect and start + connecting with your current app + + ); + default: + return <>; + } +}; diff --git a/src/Main.tsx b/src/Main.tsx index c38012b64..869f4f7ea 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -1,6 +1,7 @@ // CSS Dependencies import { initializeIcons } from "@fluentui/react"; import "bootstrap/dist/css/bootstrap.css"; +import { QuickstartTutorial } from "Explorer/Tutorials/QuickstartTutorial"; import React, { useState } from "react"; import ReactDOM from "react-dom"; import "../externals/jquery-ui.min.css"; @@ -115,6 +116,7 @@ const App: React.FunctionComponent = () => {
+
); }; diff --git a/src/hooks/useTeachingBubble.ts b/src/hooks/useTeachingBubble.ts new file mode 100644 index 000000000..003d105e2 --- /dev/null +++ b/src/hooks/useTeachingBubble.ts @@ -0,0 +1,19 @@ +import create, { UseStore } from "zustand"; + +interface TeachingBubbleState { + step: number; + isSampleDBExpanded: boolean; + isDocumentsTabOpened: boolean; + setStep: (step: number) => void; + setIsSampleDBExpanded: (isReady: boolean) => void; + setIsDocumentsTabOpened: (isOpened: boolean) => void; +} + +export const useTeachingBubble: UseStore = create((set) => ({ + step: 1, + isSampleDBExpanded: false, + isDocumentsTabOpened: false, + setStep: (step: number) => set({ step }), + setIsSampleDBExpanded: (isSampleDBExpanded: boolean) => set({ isSampleDBExpanded }), + setIsDocumentsTabOpened: (isDocumentsTabOpened: boolean) => set({ isDocumentsTabOpened }), +}));