Add condition for showing quick start carousel (#1278)

* Add condition for showing quick start carousel

* Show coach mark when carousel is closed

* Add condition for showing quick start carousel and other UI changes

* Fix compile error

* Fix issue with coach mark

* Fix test

* Add new sample data, fix link url, fix e2e tests

* Fix e2e tests
This commit is contained in:
victor-meng 2022-05-23 20:52:21 -07:00 committed by GitHub
parent d13b7a50ad
commit 46ca952955
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 241 additions and 207 deletions

View File

@ -1,26 +1,25 @@
{
"databaseId": "SampleDB",
"offerThroughput": 400,
"databaseLevelThroughput": false,
"collectionId": "Persons",
"createNewDatabase": true,
"partitionKey": { "kind": "Hash", "paths": ["/firstname"], "version": 1 },
"data": [
{
"firstname": "Eva",
"age": 44
},
{
"firstname": "Véronique",
"age": 50
},
{
"firstname": "亜妃子",
"age": 5
},
{
"firstname": "John",
"age": 23
}
{ "address": "2007, NE 37TH PL" },
{ "address": "11635, SE MAY CREEK PARK DR" },
{ "address": "8923, 133RD AVE SE" },
{ "address": "1124, N 33RD ST" },
{ "address": "4288, 131ST PL SE" },
{ "address": "10900, SE 66TH ST" },
{ "address": "6260, 139TH AVE NE" },
{ "address": "13427, NE SPRING BLVD" },
{ "address": "13812, NE SPRING BLVD" },
{ "address": "5029, 159TH PL SE" },
{ "address": "8604, 117TH AVE SE" },
{ "address": "1561, 139TH LN NE" },
{ "address": "1575, 139TH CT NE" },
{ "address": "13901, NE 15TH CT" },
{ "address": "16365, NE 12TH PL" },
{ "address": "12226, NE 37TH ST" },
{ "address": "4021, 129TH CT SE" },
{ "address": "1455, 159TH PL NE" },
{ "address": "15825, NE 14TH RD" },
{ "address": "1418, 157TH CT NE" },
{ "address": "889, 131ST PL NE" }
]
}

View File

@ -86,6 +86,7 @@ export interface Database extends TreeNode {
offer: ko.Observable<DataModels.Offer>;
isDatabaseExpanded: ko.Observable<boolean>;
isDatabaseShared: ko.Computed<boolean>;
isSampleDB?: boolean;
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
@ -112,6 +113,7 @@ export interface CollectionBase extends TreeNode {
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
children: ko.ObservableArray<TreeNode>;
isCollectionExpanded: ko.Observable<boolean>;
isSampleCollection?: boolean;
onDocumentDBDocumentsClick(): void;
onNewQueryClick(source: any, event?: MouseEvent, queryText?: string): void;

View File

@ -113,7 +113,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
this.state = {
createNewDatabase: userContext.apiType !== "Tables" && !this.props.databaseId,
newDatabaseId: props.isQuickstart ? "SampleDB" : "",
newDatabaseId: props.isQuickstart ? this.getSampleDBName() : "",
isSharedThroughputChecked: this.getSharedThroughputDefault(),
selectedDatabaseId:
userContext.apiType === "Tables" ? CollectionCreation.TablesAPIDefaultDatabase : this.props.databaseId,
@ -173,7 +173,19 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
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
<Stack>
<Text style={{ color: "white" }}>
Database is the parent of a container. You can create a new database or use an existing one. In this
tutorial we are creating a new database named SampleDB.
</Text>
<Link
style={{ color: "white", fontWeight: 600 }}
target="_blank"
href="https://aka.ms/TeachingbubbleResources"
>
Learn more about resources.
</Link>
</Stack>
</TeachingBubble>
)}
@ -187,8 +199,15 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
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
<Stack>
<Text style={{ color: "white" }}>
Cosmos DB recommends sharing throughput across database. Autoscale will give you a flexible amount of
throughput based on the max RU/s set (Request Units).
</Text>
<Link style={{ color: "white", fontWeight: 600 }} target="_blank" href="https://aka.ms/teachingbubbleRU">
Learn more about RU/s.
</Link>
</Stack>
</TeachingBubble>
)}
@ -773,18 +792,23 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
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"
/>
}
styles={{ footer: { width: "100%" } }}
>
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
<br />
<br />
<ProgressIndicator
styles={{
itemName: { color: "white" },
progressTrack: { backgroundColor: "#A6A6A6" },
progressBar: { background: "white" },
}}
label="Adding sample data set"
/>
</TeachingBubble>
)}
</div>
@ -1102,6 +1126,23 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
document.getElementById("collapsibleSectionContent")?.scrollIntoView();
}
private getSampleDBName(): string {
const existingSampleDBs = useDatabases
.getState()
.databases?.filter((database) => database.id().startsWith("SampleDB"));
const existingSampleDBNames = existingSampleDBs?.map((database) => database.id());
if (!existingSampleDBNames || existingSampleDBNames.length === 0) {
return "SampleDB";
}
let i = 1;
while (existingSampleDBNames.indexOf(`SampleDB${i}`) !== -1) {
i++;
}
return `SampleDB${i}`;
}
private async submit(event?: React.FormEvent<HTMLFormElement>): Promise<void> {
event?.preventDefault();
@ -1198,11 +1239,14 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
await createCollection(createCollectionParams);
await this.props.explorer.refreshAllDatabases();
if (this.props.isQuickstart) {
const database = useDatabases.getState().findDatabaseWithId("SampleDB");
const database = useDatabases.getState().findDatabaseWithId(databaseId);
if (database) {
database.isSampleDB = true;
// populate sample container with sample data
await database.loadCollections();
const collection = database.findCollectionWithId("SampleContainer");
const collection = database.findCollectionWithId(collectionId);
collection.isSampleCollection = true;
useTeachingBubble.getState().setSampleCollection(collection);
const sampleGenerator = await ContainerSampleGenerator.createSampleGeneratorAsync(this.props.explorer);
await sampleGenerator.populateContainerAsync(collection);
// auto-expand sample database + container and show teaching bubble

View File

@ -9,31 +9,6 @@ const createExplorer = () => {
};
describe("SplashScreen", () => {
it("allows sample collection creation for supported api's", () => {
const explorer = createExplorer();
const dataSampleUtil = new DataSamplesUtil(explorer);
const createStub = jest
.spyOn(dataSampleUtil, "createGeneratorAsync")
.mockImplementation(() => Promise.reject(undefined));
// Sample is supported
jest.spyOn(dataSampleUtil, "isSampleContainerCreationSupported").mockImplementation(() => true);
const splashScreen = new SplashScreen({ explorer });
jest.spyOn(splashScreen, "createDataSampleUtil").mockImplementation(() => dataSampleUtil);
const mainButtons = splashScreen.createMainItems();
// Press all buttons and make sure create gets called
mainButtons.forEach((button) => {
try {
button.onClick();
} catch (e) {
// noop
}
});
expect(createStub).toHaveBeenCalled();
});
it("does not allow sample collection creation for non-supported api's", () => {
const explorerStub = createExplorer();
const dataSampleUtil = new DataSamplesUtil(explorerStub);

View File

@ -2,6 +2,7 @@
* Accordion top class
*/
import { Coachmark, DirectionalHint, Image, Link, Stack, TeachingBubbleContent, Text } from "@fluentui/react";
import { useCarousel } from "hooks/useCarousel";
import { useTabs } from "hooks/useTabs";
import * as React from "react";
import AddDatabaseIcon from "../../../images/AddDatabase.svg";
@ -10,9 +11,6 @@ import NewStoredProcedureIcon from "../../../images/AddStoredProcedure.svg";
import OpenQueryIcon from "../../../images/BrowseQuery.svg";
import ConnectIcon from "../../../images/Connect_color.svg";
import ContainersIcon from "../../../images/Containers.svg";
import NewContainerIcon from "../../../images/Hero-new-container.svg";
import NewNotebookIcon from "../../../images/Hero-new-notebook.svg";
import SampleIcon from "../../../images/Hero-sample.svg";
import LinkIcon from "../../../images/Link_blue.svg";
import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
import NotebookColorIcon from "../../../images/Notebooks.svg";
@ -49,11 +47,7 @@ export interface SplashScreenProps {
explorer: Explorer;
}
export interface SplashScreenState {
showCoachmark: boolean;
}
export class SplashScreen extends React.Component<SplashScreenProps, SplashScreenState> {
export class SplashScreen extends React.Component<SplashScreenProps> {
private static readonly seeMoreItemTitle: string = "See more Cosmos DB documentation";
private static readonly seeMoreItemUrl: string = "https://aka.ms/cosmosdbdocument";
private static readonly dataModelingUrl = "https://docs.microsoft.com/azure/cosmos-db/modeling-data";
@ -67,10 +61,6 @@ export class SplashScreen extends React.Component<SplashScreenProps, SplashScree
super(props);
this.container = props.explorer;
this.subscriptions = [];
this.state = {
showCoachmark: userContext.features.enableNewQuickstart,
};
}
public componentWillUnmount(): void {
@ -87,7 +77,13 @@ export class SplashScreen extends React.Component<SplashScreenProps, SplashScree
(state) => state.isNotebookEnabled
),
},
{ dispose: useSelectedNode.subscribe(() => this.setState({})) }
{ dispose: useSelectedNode.subscribe(() => this.setState({})) },
{
dispose: useCarousel.subscribe(
() => this.setState({}),
(state) => state.showCoachMark
),
}
);
}
@ -129,17 +125,14 @@ export class SplashScreen extends React.Component<SplashScreenProps, SplashScree
{item.showLinkIcon && <Image style={{ marginLeft: 8, width: 16 }} src={LinkIcon} />}
</Stack>
<div
id={item.id}
className={userContext.features.enableNewQuickstart ? "newDescription" : "description"}
>
<div id={item.id} className="newDescription">
{item.description}
</div>
</div>
</Stack>
))}
</div>
{this.state.showCoachmark && (
{useCarousel.getState().showCoachMark && (
<Coachmark
target="#quickstartDescription"
positioningContainerProps={{ directionalHint: DirectionalHint.rightTopEdge }}
@ -152,34 +145,33 @@ export class SplashScreen extends React.Component<SplashScreenProps, SplashScree
primaryButtonProps={{
text: "Get started",
onClick: () => {
this.setState({ showCoachmark: false });
useCarousel.getState().setShowCoachMark(false);
this.container.onNewCollectionClicked({ isQuickstart: true });
},
}}
secondaryButtonProps={{ text: "Cancel", onClick: () => this.setState({ showCoachmark: false }) }}
onDismiss={() => this.setState({ showCoachmark: false })}
secondaryButtonProps={{
text: "Cancel",
onClick: () => useCarousel.getState().setShowCoachMark(false),
}}
onDismiss={() => useCarousel.getState().setShowCoachMark(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
data explorer. You can also cancel launching this tour and explore yourself
</TeachingBubbleContent>
</Coachmark>
)}
<div className="moreStuffContainer">
<div className="moreStuffColumn commonTasks">
<div className="title">{userContext.features.enableNewQuickstart ? "Recents" : "Common Tasks"}</div>
{userContext.features.enableNewQuickstart ? this.getRecentItems() : this.getCommonTasksItems()}
<div className="title">Recents</div>
{this.getRecentItems()}
</div>
<div className="moreStuffColumn">
<div className="title">
{userContext.features.enableNewQuickstart ? "Top 3 things you need to know" : "Recents"}
</div>
{userContext.features.enableNewQuickstart ? this.top3Items() : this.getRecentItems()}
<div className="title">Top 3 things you need to know</div>
{this.top3Items()}
</div>
<div className="moreStuffColumn tipsContainer">
<div className="title">
{userContext.features.enableNewQuickstart ? "Learning Resources" : "Tips"}
</div>
{userContext.features.enableNewQuickstart ? this.getLearningResourceItems() : this.getTipItems()}
<div className="title">Learning Resources</div>
{this.getLearningResourceItems()}
</div>
</div>
</div>
@ -202,73 +194,45 @@ export class SplashScreen extends React.Component<SplashScreenProps, SplashScree
public createMainItems(): SplashScreenItem[] {
const heroes: SplashScreenItem[] = [];
if (userContext.features.enableNewQuickstart) {
if (userContext.apiType === "SQL" || userContext.apiType === "Mongo") {
const launchQuickstartBtn = {
id: "quickstartDescription",
iconSrc: QuickStartIcon,
title: "Launch quick start",
description: "Launch a quick start tutorial to get started with sample data",
showLinkIcon: userContext.apiType === "Mongo",
onClick: () =>
userContext.apiType === "Mongo"
? window.open("http://aka.ms/mongodbquickstart", "_blank")
: this.container.onNewCollectionClicked({ isQuickstart: true }),
};
heroes.push(launchQuickstartBtn);
} else if (useNotebook.getState().isPhoenixNotebooks) {
const newNotebookBtn = {
iconSrc: NotebookColorIcon,
title: "New notebook",
description: "Visualize your data stored in Azure Cosmos DB",
onClick: () => this.container.onNewNotebookClicked(),
};
heroes.push(newNotebookBtn);
}
const newContainerBtn = {
iconSrc: ContainersIcon,
title: `New ${getCollectionName()}`,
description: "Create a new container for storage and throughput",
onClick: () => this.container.onNewCollectionClicked(),
if (userContext.apiType === "SQL" || userContext.apiType === "Mongo") {
const launchQuickstartBtn = {
id: "quickstartDescription",
iconSrc: QuickStartIcon,
title: "Launch quick start",
description: "Launch a quick start tutorial to get started with sample data",
showLinkIcon: userContext.apiType === "Mongo",
onClick: () =>
userContext.apiType === "Mongo"
? window.open("http://aka.ms/mongodbquickstart", "_blank")
: this.container.onNewCollectionClicked({ isQuickstart: true }),
};
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",
onClick: () => useTabs.getState().openAndActivateConnectTab(),
heroes.push(launchQuickstartBtn);
} else if (useNotebook.getState().isPhoenixNotebooks) {
const newNotebookBtn = {
iconSrc: NotebookColorIcon,
title: "New notebook",
description: "Visualize your data stored in Azure Cosmos DB",
onClick: () => this.container.onNewNotebookClicked(),
};
heroes.push(connectBtn);
} else {
const dataSampleUtil = this.createDataSampleUtil();
if (dataSampleUtil.isSampleContainerCreationSupported()) {
heroes.push({
iconSrc: SampleIcon,
title: "Start with Sample",
description: "Get started with a sample provided by Cosmos DB",
onClick: () => dataSampleUtil.createSampleContainerAsync(),
});
}
heroes.push({
iconSrc: NewContainerIcon,
title: `New ${getCollectionName()}`,
description: "Create a new container for storage and throughput",
onClick: () => this.container.onNewCollectionClicked(),
});
if (useNotebook.getState().isPhoenixNotebooks) {
heroes.push({
iconSrc: NewNotebookIcon,
title: "New Notebook",
description: "Create a notebook to start querying, visualizing, and modeling your data",
onClick: () => this.container.onNewNotebookClicked(),
});
}
heroes.push(newNotebookBtn);
}
const newContainerBtn = {
iconSrc: ContainersIcon,
title: `New ${getCollectionName()}`,
description: "Create a new container for storage and throughput",
onClick: () => this.container.onNewCollectionClicked(),
};
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",
onClick: () => useTabs.getState().openAndActivateConnectTab(),
};
heroes.push(connectBtn);
return heroes;
}

View File

@ -1,14 +1,4 @@
import {
IconButton,
ITextFieldStyles,
Link,
Pivot,
PivotItem,
PrimaryButton,
Stack,
Text,
TextField,
} from "@fluentui/react";
import { IconButton, ITextFieldStyles, Pivot, PivotItem, PrimaryButton, Stack, Text, TextField } from "@fluentui/react";
import { handleError } from "Common/ErrorHandlingUtils";
import { sendMessage } from "Common/MessageHandler";
import { MessageTypes } from "Contracts/ExplorerContracts";
@ -76,16 +66,6 @@ export const ConnectTab: React.FC = (): JSX.Element => {
return (
<div style={{ width: "100%", padding: 16 }}>
<Stack style={{ marginLeft: 10 }}>
<Text variant="medium">
Ensure you have the right networking / access configuration before you establish the connection with your app
or 3rd party tool.
</Text>
<Link style={{ fontSize: 14 }} target="_blank" href="">
Configure networking in Azure portal
</Link>
</Stack>
<Pivot>
{userContext.hasWriteAccess && (
<PivotItem headerText="Read-write Keys">

View File

@ -121,11 +121,7 @@ function TabPane({ tab, active }: { tab: Tab; active: boolean }) {
};
useEffect((): (() => void) | void => {
if (
tab.tabKind === CollectionTabKind.Documents &&
tab.collection?.databaseId === "SampleDB" &&
tab.collection?.id() === "SampleContainer"
) {
if (tab.tabKind === CollectionTabKind.Documents && tab.collection?.isSampleCollection) {
useTeachingBubble.getState().setIsDocumentsTabOpened(true);
}

View File

@ -97,6 +97,7 @@ export default class Collection implements ViewModels.Collection {
public storedProceduresFocused: ko.Observable<boolean>;
public userDefinedFunctionsFocused: ko.Observable<boolean>;
public triggersFocused: ko.Observable<boolean>;
public isSampleCollection: boolean;
private isOfferRead: boolean;
constructor(container: Explorer, databaseId: string, data: DataModels.Collection) {
@ -216,6 +217,7 @@ export default class Collection implements ViewModels.Collection {
this.isStoredProceduresExpanded = ko.observable<boolean>(false);
this.isUserDefinedFunctionsExpanded = ko.observable<boolean>(false);
this.isTriggersExpanded = ko.observable<boolean>(false);
this.isSampleCollection = false;
this.isOfferRead = false;
}

View File

@ -37,6 +37,7 @@ export default class Database implements ViewModels.Database {
public isDatabaseShared: ko.Computed<boolean>;
public selectedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>;
public junoClient: JunoClient;
public isSampleDB: boolean;
private isOfferRead: boolean;
constructor(container: Explorer, data: DataModels.Database) {
@ -54,6 +55,7 @@ export default class Database implements ViewModels.Database {
return this.offer && !!this.offer();
});
this.junoClient = new JunoClient();
this.isSampleDB = false;
this.isOfferRead = false;
}

View File

@ -1,5 +1,4 @@
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";
@ -462,7 +461,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
if (database.isDatabaseShared()) {
databaseNode.children.push({
id: database.id() === "SampleDB" ? "sampleScaleSettings" : "",
id: database.isSampleDB ? "sampleScaleSettings" : "",
label: "Scale",
isSelected: () =>
useSelectedNode
@ -499,7 +498,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" : "",
id: collection.isSampleCollection ? "sampleItems" : "",
onClick: () => {
collection.openTab();
// push to most recent
@ -533,10 +532,7 @@ 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"
: "",
id: collection.isSampleCollection && !database.isDatabaseShared() ? "sampleScaleSettings" : "",
label: database.isDatabaseShared() || isServerlessAccount() ? "Settings" : "Scale & Settings",
onClick: collection.onSettingsClick.bind(collection),
isSelected: () =>
@ -593,10 +589,6 @@ 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

@ -1,6 +1,8 @@
import { DefaultButton, IconButton, Image, Modal, PrimaryButton, Stack, Text } from "@fluentui/react";
import { useCarousel } from "hooks/useCarousel";
import React, { useState } from "react";
import Youtube from "react-youtube";
import { userContext } from "UserContext";
import Image1 from "../../../images/CarouselImage1.svg";
import Image2 from "../../../images/CarouselImage2.svg";
@ -13,7 +15,11 @@ export const QuickstartCarousel: React.FC<QuickstartCarouselProps> = ({
}: QuickstartCarouselProps): JSX.Element => {
const [page, setPage] = useState<number>(1);
return (
<Modal styles={{ main: { width: 640 } }} isOpen={isOpen && page < 4}>
<Modal
styles={{ main: { width: 640 } }}
isOpen={isOpen && page < 4}
onDismissed={() => userContext.apiType === "SQL" && useCarousel.getState().setShowCoachMark(true)}
>
<Stack>
<Stack horizontal horizontalAlign="space-between" style={{ padding: 16 }}>
<Text variant="xLarge">{getHeaderText(page)}</Text>
@ -28,9 +34,23 @@ export const QuickstartCarousel: React.FC<QuickstartCarouselProps> = ({
<DefaultButton text="Previous" style={{ margin: "16px 8px 16px 0" }} onClick={() => setPage(page - 1)} />
)}
<PrimaryButton
id="carouselNextBtn"
style={{ margin: "16px 16px 16px 0" }}
text={page === 3 ? "Finish" : "Next"}
onClick={() => setPage(page + 1)}
onClick={() => {
if (
userContext.apiType === "Cassandra" ||
userContext.apiType === "Tables" ||
userContext.apiType === "Gremlin"
) {
setPage(page + 2);
} else {
if (page === 3 && userContext.apiType === "SQL") {
useCarousel.getState().setShowCoachMark(true);
}
setPage(page + 1);
}
}}
/>
</Stack>
</Stack>

View File

@ -1,11 +1,10 @@
import { TeachingBubble } from "@fluentui/react";
import { useDatabases } from "Explorer/useDatabases";
import { Link, Stack, TeachingBubble, Text } from "@fluentui/react";
import { useTabs } from "hooks/useTabs";
import { useTeachingBubble } from "hooks/useTeachingBubble";
import React from "react";
export const QuickstartTutorial: React.FC = (): JSX.Element => {
const { step, isSampleDBExpanded, isDocumentsTabOpened, setStep } = useTeachingBubble();
const { step, isSampleDBExpanded, isDocumentsTabOpened, sampleCollection, setStep } = useTeachingBubble();
switch (step) {
case 1:
@ -17,8 +16,7 @@ export const QuickstartTutorial: React.FC = (): JSX.Element => {
primaryButtonProps={{
text: "Open Items",
onClick: () => {
const sampleContainer = useDatabases.getState().findCollection("SampleDB", "SampleContainer");
sampleContainer.openTab();
sampleCollection.openTab();
setStep(2);
},
}}
@ -70,7 +68,7 @@ export const QuickstartTutorial: React.FC = (): JSX.Element => {
onDismiss={() => setStep(0)}
footerContent="Step 3 of 7"
>
Add new item by copy / pasting jsons; or uploading a json
Add new item by copy / pasting JSON; or uploading a JSON
</TeachingBubble>
);
case 4:
@ -120,7 +118,7 @@ export const QuickstartTutorial: React.FC = (): JSX.Element => {
target={"#newNotebookBtn"}
hasCloseButton
primaryButtonProps={{
text: "Finish",
text: "Next",
onClick: () => setStep(7),
}}
secondaryButtonProps={{
@ -150,8 +148,15 @@ export const QuickstartTutorial: React.FC = (): JSX.Element => {
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
<Stack>
<Text style={{ color: "white" }}>
You have finished the tour in data explorer. For next steps, you may want to launch connect and start
connecting with your current app.
</Text>
<Link style={{ color: "white", fontWeight: 600 }} target="_blank" href="https://aka.ms/cosmosdbsurvey">
Share your feedback
</Link>
</Stack>
</TeachingBubble>
);
default:

View File

@ -3,9 +3,9 @@ import { initializeIcons } from "@fluentui/react";
import "bootstrap/dist/css/bootstrap.css";
import { QuickstartCarousel } from "Explorer/Tutorials/QuickstartCarousel";
import { QuickstartTutorial } from "Explorer/Tutorials/QuickstartTutorial";
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";
@ -61,6 +61,7 @@ const App: React.FunctionComponent = () => {
const [isLeftPaneExpanded, setIsLeftPaneExpanded] = useState<boolean>(true);
const openedTabs = useTabs((state) => state.openedTabs);
const isConnectTabOpen = useTabs((state) => state.isConnectTabOpen);
const isCarouselOpen = useCarousel((state) => state.shouldOpen);
const config = useConfig();
const explorer = useKnockoutExplorer(config?.platform);
@ -119,8 +120,8 @@ const App: React.FunctionComponent = () => {
</div>
<SidePanel />
<Dialog />
{userContext.features.enableNewQuickstart && <QuickstartCarousel isOpen={true} />}
{userContext.features.enableNewQuickstart && <QuickstartTutorial />}
{<QuickstartCarousel isOpen={isCarouselOpen} />}
{<QuickstartTutorial />}
</div>
);
};

View File

@ -29,7 +29,6 @@ export type Features = {
readonly mongoProxyEndpoint?: string;
readonly mongoProxyAPIs?: string;
readonly enableThroughputCap: boolean;
readonly enableNewQuickstart: boolean;
// can be set via both flight and feature flag
autoscaleDefault: boolean;
@ -91,7 +90,6 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
partitionKeyDefault2: "true" === get("pkpartitionkeytest"),
notebooksDownBanner: "true" === get("notebooksDownBanner"),
enableThroughputCap: "true" === get("enablethroughputcap"),
enableNewQuickstart: "true" === get("enablenewquickstart"),
};
}

View File

@ -14,4 +14,5 @@ export enum StorageKey {
MostRecentActivity,
SetPartitionKeyUndefined,
GalleryCalloutDismissed,
VisitedAccounts,
}

View File

@ -1,3 +1,4 @@
import { useCarousel } from "hooks/useCarousel";
import { AuthType } from "./AuthType";
import { DatabaseAccount } from "./Contracts/DataModels";
import { SubscriptionType } from "./Contracts/SubscriptionType";
@ -73,6 +74,10 @@ const userContext: UserContext = {
function updateUserContext(newContext: Partial<UserContext>): void {
if (newContext.databaseAccount) {
newContext.apiType = apiType(newContext.databaseAccount);
if (!localStorage.getItem(newContext.databaseAccount.id)) {
useCarousel.getState().setShouldOpen(true);
localStorage.setItem(newContext.databaseAccount.id, "true");
}
}
Object.assign(userContext, newContext);
}

15
src/hooks/useCarousel.ts Normal file
View File

@ -0,0 +1,15 @@
import create, { UseStore } from "zustand";
interface CarouselState {
shouldOpen: boolean;
showCoachMark: boolean;
setShouldOpen: (shouldOpen: boolean) => void;
setShowCoachMark: (showCoachMark: boolean) => void;
}
export const useCarousel: UseStore<CarouselState> = create((set) => ({
shouldOpen: false,
showCoachMark: false,
setShouldOpen: (shouldOpen: boolean) => set({ shouldOpen }),
setShowCoachMark: (showCoachMark: boolean) => set({ showCoachMark }),
}));

View File

@ -1,19 +1,24 @@
import { Collection } from "Contracts/ViewModels";
import create, { UseStore } from "zustand";
interface TeachingBubbleState {
step: number;
isSampleDBExpanded: boolean;
isDocumentsTabOpened: boolean;
sampleCollection: Collection;
setStep: (step: number) => void;
setIsSampleDBExpanded: (isReady: boolean) => void;
setIsDocumentsTabOpened: (isOpened: boolean) => void;
setSampleCollection: (sampleCollection: Collection) => void;
}
export const useTeachingBubble: UseStore<TeachingBubbleState> = create((set) => ({
step: 1,
isSampleDBExpanded: false,
isDocumentsTabOpened: false,
sampleCollection: undefined,
setStep: (step: number) => set({ step }),
setIsSampleDBExpanded: (isSampleDBExpanded: boolean) => set({ isSampleDBExpanded }),
setIsDocumentsTabOpened: (isDocumentsTabOpened: boolean) => set({ isDocumentsTabOpened }),
setSampleCollection: (sampleCollection: Collection) => set({ sampleCollection }),
}));

View File

@ -13,6 +13,10 @@ test("Cassandra keyspace and table CRUD", async () => {
await page.waitForSelector("iframe");
const explorer = await waitForExplorer();
// Click through quick start carousel
await explorer.click("#carouselNextBtn");
await explorer.click("#carouselNextBtn");
await explorer.click('[data-test="New Table"]');
await explorer.click('[aria-label="Keyspace id"]');
await explorer.fill('[aria-label="Keyspace id"]', keyspaceId);

View File

@ -12,6 +12,10 @@ test("Graph CRUD", async () => {
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-gremlin-runner");
const explorer = await waitForExplorer();
// Click through quick start carousel
await explorer.click("#carouselNextBtn");
await explorer.click("#carouselNextBtn");
// Create new database and graph
await explorer.click('[data-test="New Graph"]');
await explorer.fill('[aria-label="New database id"]', databaseId);

View File

@ -12,6 +12,11 @@ test("Mongo CRUD", async () => {
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo-runner");
const explorer = await waitForExplorer();
// Click through quick start carousel
await explorer.click("#carouselNextBtn");
await explorer.click("#carouselNextBtn");
await explorer.click("#carouselNextBtn");
// Create new database and collection
await explorer.click('[data-test="New Collection"]');
await explorer.fill('[aria-label="New database id"]', databaseId);

View File

@ -12,6 +12,11 @@ test("Mongo CRUD", async () => {
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo32-runner");
const explorer = await waitForExplorer();
// Click through quick start carousel
await explorer.click("#carouselNextBtn");
await explorer.click("#carouselNextBtn");
await explorer.click("#carouselNextBtn");
// Create new database and collection
await explorer.click('[data-test="New Collection"]');
await explorer.fill('[aria-label="New database id"]', databaseId);

View File

@ -11,6 +11,12 @@ test("SQL CRUD", async () => {
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner-west-us");
const explorer = await waitForExplorer();
// Click through quick start carousel
await explorer.click("#carouselNextBtn");
await explorer.click("#carouselNextBtn");
await explorer.click("#carouselNextBtn");
await explorer.click('[data-test="New Container"]');
await explorer.fill('[aria-label="New database id"]', databaseId);
await explorer.fill('[aria-label="Container id"]', containerId);

View File

@ -12,6 +12,10 @@ test("Tables CRUD", async () => {
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-tables-runner");
const explorer = await waitForExplorer();
// Click through quick start carousel
await explorer.click("#carouselNextBtn");
await explorer.click("#carouselNextBtn");
await page.waitForSelector('text="Querying databases"', { state: "detached" });
await explorer.click('[data-test="New Table"]');
await explorer.fill('[aria-label="Table id"]', tableId);