Add teaching bubbles after creating sample DB (#1270)

* Add teaching bubbles after creating sample DB

* Add teaching bubble while creating sample container

* Remove test code

* Update tests and always show teaching bubbles in add collection panel when launched from quick start

* Fix snapshot
This commit is contained in:
victor-meng 2022-05-16 17:45:50 -07:00 committed by GitHub
parent 37122acc33
commit 60525f654b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 253 additions and 11 deletions

View File

@ -173,6 +173,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
onClick={(event: React.MouseEvent<HTMLDivElement>) => this.onNodeClick(event, node)}
onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) => this.onNodeKeyPress(event, node)}
role="treeitem"
id={node.id}
>
<div
className={`treeNodeHeader ${this.state.isMenuShowing ? "showingMenu" : ""}`}

View File

@ -137,6 +137,7 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`] = `
<div
className="nodeClassname main12 nodeItem "
id="id"
onClick={[Function]}
onKeyPress={[Function]}
role="treeitem"
@ -359,6 +360,7 @@ exports[`TreeNodeComponent renders loading icon 1`] = `
exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents separated 1`] = `
<div
className="nodeClassname main12 nodeItem "
id="id"
onClick={[Function]}
onKeyPress={[Function]}
role="treeitem"

View File

@ -298,7 +298,7 @@ export default class Explorer {
db1.id().localeCompare(db2.id())
);
useDatabases.setState({ databases: updatedDatabases });
await this.refreshAndExpandNewDatabases(deltaDatabases.toAdd, currentDatabases);
await this.refreshAndExpandNewDatabases(deltaDatabases.toAdd, updatedDatabases);
} catch (error) {
const errorMessage = getErrorMessage(error);
TelemetryProcessor.traceFailure(
@ -1135,7 +1135,6 @@ export default class Explorer {
options: {
databaseId?: string;
isQuickstart?: boolean;
showTeachingBubble?: boolean;
} = {}
): Promise<void> {
if (userContext.apiType === "Cassandra") {

View File

@ -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(),

View File

@ -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<AddCollectionPanelProps,
}
componentDidMount(): void {
if (this.state.teachingBubbleStep === 0 && this.props.showTeachingBubble) {
if (this.state.teachingBubbleStep === 0 && this.props.isQuickstart) {
this.setState({ teachingBubbleStep: 1 });
}
}
@ -213,7 +214,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
primaryButtonProps={{
text: "Create container",
onClick: () => {
this.setState({ teachingBubbleStep: 0 });
this.setState({ teachingBubbleStep: 5 });
this.submit();
},
}}
@ -764,7 +765,30 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
<PanelFooterComponent buttonLabel="OK" isButtonDisabled={this.state.isThroughputCapExceeded} />
{this.state.isExecuting && <PanelLoadingScreen />}
{this.state.isExecuting && (
<div>
<PanelLoadingScreen />
{this.state.teachingBubbleStep === 5 && (
<TeachingBubble
headline="Creating sample container"
target={"#loadingScreen"}
onDismiss={() => this.setState({ teachingBubbleStep: 0 })}
footerContent={
<ProgressIndicator
styles={{ itemName: { color: "rgb(255, 255, 255)" } }}
label="Adding sample data set"
/>
}
>
A sample container is now being created and we are adding sample data for you. It should take about 1
minute.
<br />
<br />
Once the sample container is created, review your sample dataset and follow next steps
</TeachingBubble>
)}
</div>
)}
</form>
);
}
@ -1176,10 +1200,16 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
if (this.props.isQuickstart) {
const database = useDatabases.getState().findDatabaseWithId("SampleDB");
if (database) {
// populate sample container with sample data
await database.loadCollections();
const collection = database.findCollectionWithId("SampleContainer");
const sampleGenerator = await ContainerSampleGenerator.createSampleGeneratorAsync(this.props.explorer);
sampleGenerator.populateContainerAsync(collection);
await sampleGenerator.populateContainerAsync(collection);
// auto-expand sample database + container and show teaching bubble
await database.expandDatabase();
collection.expandCollection();
useDatabases.getState().updateDatabase(database);
useTeachingBubble.getState().setIsSampleDBExpanded(true);
}
}
this.setState({ isExecuting: false });

View File

@ -2,7 +2,7 @@ import React from "react";
import LoadingIndicator_3Squares from "../../../images/LoadingIndicator_3Squares.gif";
export const PanelLoadingScreen: React.FunctionComponent = () => (
<div className="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer">
<div id="loadingScreen" className="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer">
<img className="dataExplorerLoader" src={LoadingIndicator_3Squares} />
</div>
);

View File

@ -149,7 +149,7 @@ export class SplashScreen extends React.Component<SplashScreenProps, SplashScree
text: "Get started",
onClick: () => {
this.setState({ showCoachmark: false });
this.container.onNewCollectionClicked({ isQuickstart: true, showTeachingBubble: true });
this.container.onNewCollectionClicked({ isQuickstart: true });
},
}}
secondaryButtonProps={{ text: "Cancel", onClick: () => this.setState({ showCoachmark: false }) }}

View File

@ -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: () => {

View File

@ -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);

View File

@ -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<ResourceTreeProps> = ({ 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<ResourceTreeProps> = ({ 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<ResourceTreeProps> = ({ 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<ResourceTreeProps> = ({ 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<ResourceTreeProps> = ({ 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<ResourceTreeProps> = ({ 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();

View File

@ -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 ? (
<TeachingBubble
headline="View sample data"
target={"#sampleItems"}
hasCloseButton
primaryButtonProps={{
text: "Open Items",
onClick: () => {
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
</TeachingBubble>
) : (
<></>
);
case 2:
return isDocumentsTabOpened ? (
<TeachingBubble
headline="View item"
target={".queryButton"}
hasCloseButton
primaryButtonProps={{
text: "Next",
onClick: () => 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
</TeachingBubble>
) : (
<></>
);
case 3:
return (
<TeachingBubble
headline="Add new item"
target={"#uploadItemBtn"}
hasCloseButton
primaryButtonProps={{
text: "Next",
onClick: () => 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
</TeachingBubble>
);
case 4:
return (
<TeachingBubble
headline="Run a query"
target={"#newQueryBtn"}
hasCloseButton
primaryButtonProps={{
text: "Next",
onClick: () => 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.
</TeachingBubble>
);
case 5:
return (
<TeachingBubble
headline="Scale throughput"
target={"#sampleScaleSettings"}
hasCloseButton
primaryButtonProps={{
text: "Next",
onClick: () => 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
</TeachingBubble>
);
case 6:
return (
<TeachingBubble
headline="Create notebook"
target={"#newNotebookBtn"}
hasCloseButton
primaryButtonProps={{
text: "Finish",
onClick: () => 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
</TeachingBubble>
);
case 7:
return (
<TeachingBubble
headline="Congratulations!"
target={"#newNotebookBtn"}
hasCloseButton
primaryButtonProps={{
text: "Launch connect",
//onClick: () => 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
</TeachingBubble>
);
default:
return <></>;
}
};

View File

@ -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 = () => {
</div>
<SidePanel />
<Dialog />
<QuickstartTutorial />
</div>
);
};

View File

@ -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<TeachingBubbleState> = create((set) => ({
step: 1,
isSampleDBExpanded: false,
isDocumentsTabOpened: false,
setStep: (step: number) => set({ step }),
setIsSampleDBExpanded: (isSampleDBExpanded: boolean) => set({ isSampleDBExpanded }),
setIsDocumentsTabOpened: (isDocumentsTabOpened: boolean) => set({ isDocumentsTabOpened }),
}));