mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-24 22:46:40 +00:00
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:
parent
d13b7a50ad
commit
46ca952955
@ -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" }
|
||||
]
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
>
|
||||
<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
|
||||
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
|
||||
|
@ -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);
|
||||
|
@ -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,7 +194,6 @@ 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",
|
||||
@ -241,33 +232,6 @@ export class SplashScreen extends React.Component<SplashScreenProps, SplashScree
|
||||
onClick: () => useTabs.getState().openAndActivateConnectTab(),
|
||||
};
|
||||
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(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return heroes;
|
||||
}
|
||||
|
@ -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">
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
>
|
||||
<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
|
||||
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:
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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"),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -14,4 +14,5 @@ export enum StorageKey {
|
||||
MostRecentActivity,
|
||||
SetPartitionKeyUndefined,
|
||||
GalleryCalloutDismissed,
|
||||
VisitedAccounts,
|
||||
}
|
||||
|
@ -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
15
src/hooks/useCarousel.ts
Normal 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 }),
|
||||
}));
|
@ -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 }),
|
||||
}));
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user