mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-02-16 17:25:58 +00:00
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:
parent
37122acc33
commit
60525f654b
@ -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" : ""}`}
|
||||
|
@ -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"
|
||||
|
@ -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") {
|
||||
|
@ -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(),
|
||||
|
@ -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 });
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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 }) }}
|
||||
|
@ -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: () => {
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
164
src/Explorer/Tutorials/QuickstartTutorial.tsx
Normal file
164
src/Explorer/Tutorials/QuickstartTutorial.tsx
Normal 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 <></>;
|
||||
}
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
19
src/hooks/useTeachingBubble.ts
Normal file
19
src/hooks/useTeachingBubble.ts
Normal 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 }),
|
||||
}));
|
Loading…
x
Reference in New Issue
Block a user