New quick start - create sample container (#1259)

* Update links and texts

* Remove unintended change

* Quick start - create sample container

* Small adjustments + fix test

* Hide coach mark behind feature flag

* Add snapshot test for AddCollectionPanel to increase coverage

* Fix snapshot

* Fix snapshot 2...

* Change runner account name

* Change portal runner account name
This commit is contained in:
victor-meng 2022-05-04 18:24:34 -07:00 committed by GitHub
parent d05a05716f
commit ebbfc5f517
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 603 additions and 24 deletions

View File

@ -39,7 +39,7 @@ export const createDatabaseContextMenu = (container: Explorer, databaseId: strin
const items: TreeNodeMenuItem[] = [ const items: TreeNodeMenuItem[] = [
{ {
iconSrc: AddCollectionIcon, iconSrc: AddCollectionIcon,
onClick: () => container.onNewCollectionClicked(databaseId), onClick: () => container.onNewCollectionClicked({ databaseId }),
label: `New ${getCollectionName()}`, label: `New ${getCollectionName()}`,
}, },
]; ];

View File

@ -16,6 +16,7 @@ export interface ThroughputInputProps {
isSharded: boolean; isSharded: boolean;
isFreeTier: boolean; isFreeTier: boolean;
showFreeTierExceedThroughputTooltip: boolean; showFreeTierExceedThroughputTooltip: boolean;
isQuickstart?: boolean;
setThroughputValue: (throughput: number) => void; setThroughputValue: (throughput: number) => void;
setIsAutoscale: (isAutoscale: boolean) => void; setIsAutoscale: (isAutoscale: boolean) => void;
setIsThroughputCapExceeded: (isThroughputCapExceeded: boolean) => void; setIsThroughputCapExceeded: (isThroughputCapExceeded: boolean) => void;
@ -226,6 +227,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
</Stack> </Stack>
<TextField <TextField
id="autoscaleRUValueField"
type="number" type="number"
styles={{ styles={{
fieldGroup: { width: 300, height: 27 }, fieldGroup: { width: 300, height: 27 },

View File

@ -1637,6 +1637,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
<StyledTextFieldBase <StyledTextFieldBase
aria-label="Max request units per second" aria-label="Max request units per second"
errorMessage="" errorMessage=""
id="autoscaleRUValueField"
key=".0:$.2" key=".0:$.2"
min={1000} min={1000}
onChange={[Function]} onChange={[Function]}
@ -1660,6 +1661,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-label="Max request units per second" aria-label="Max request units per second"
deferredValidationTime={200} deferredValidationTime={200}
errorMessage="" errorMessage=""
id="autoscaleRUValueField"
min={1000} min={1000}
onChange={[Function]} onChange={[Function]}
required={true} required={true}
@ -1955,7 +1957,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
<input <input
aria-invalid={false} aria-invalid={false}
className="ms-TextField-field field-64" className="ms-TextField-field field-64"
id="TextField2" id="autoscaleRUValueField"
min={1000} min={1000}
onBlur={[Function]} onBlur={[Function]}
onChange={[Function]} onChange={[Function]}

View File

@ -68,11 +68,10 @@ export class ContainerSampleGenerator {
return database.findCollectionWithId(this.sampleDataFile.collectionId); return database.findCollectionWithId(this.sampleDataFile.collectionId);
} }
private async populateContainerAsync(collection: ViewModels.Collection): Promise<void> { public async populateContainerAsync(collection: ViewModels.Collection): Promise<void> {
if (!collection) { if (!collection) {
throw new Error("No container to populate"); throw new Error("No container to populate");
} }
const promises: Q.Promise<any>[] = [];
if (userContext.apiType === "Gremlin") { if (userContext.apiType === "Gremlin") {
// For Gremlin, all queries are executed sequentially, because some queries might be dependent on other queries // For Gremlin, all queries are executed sequentially, because some queries might be dependent on other queries

View File

@ -1131,7 +1131,13 @@ export default class Explorer {
} }
} }
public async onNewCollectionClicked(databaseId?: string): Promise<void> { public async onNewCollectionClicked(
options: {
databaseId?: string;
isQuickstart?: boolean;
showTeachingBubble?: boolean;
} = {}
): Promise<void> {
if (userContext.apiType === "Cassandra") { if (userContext.apiType === "Cassandra") {
useSidePanel useSidePanel
.getState() .getState()
@ -1146,7 +1152,7 @@ export default class Explorer {
: await useDatabases.getState().loadDatabaseOffers(); : await useDatabases.getState().loadDatabaseOffers();
useSidePanel useSidePanel
.getState() .getState()
.openSidePanel("New " + getCollectionName(), <AddCollectionPanel explorer={this} databaseId={databaseId} />); .openSidePanel("New " + getCollectionName(), <AddCollectionPanel explorer={this} {...options} />);
} }
} }

View File

@ -0,0 +1,15 @@
import { shallow } from "enzyme";
import React from "react";
import Explorer from "../Explorer";
import { AddCollectionPanel } from "./AddCollectionPanel";
const props = {
explorer: new Explorer(),
};
describe("AddCollectionPanel", () => {
it("should render Default properly", () => {
const wrapper = shallow(<AddCollectionPanel {...props} />);
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -10,6 +10,7 @@ import {
Link, Link,
Separator, Separator,
Stack, Stack,
TeachingBubble,
Text, Text,
TooltipHost, TooltipHost,
} from "@fluentui/react"; } from "@fluentui/react";
@ -30,6 +31,7 @@ import { isCapabilityEnabled, isServerlessAccount } from "Utils/CapabilityUtils"
import { getUpsellMessage } from "Utils/PricingUtils"; import { getUpsellMessage } from "Utils/PricingUtils";
import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent"; import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent";
import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput"; import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput";
import { ContainerSampleGenerator } from "../DataSamples/ContainerSampleGenerator";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { useDatabases } from "../useDatabases"; import { useDatabases } from "../useDatabases";
import { PanelFooterComponent } from "./PanelFooterComponent"; import { PanelFooterComponent } from "./PanelFooterComponent";
@ -39,6 +41,8 @@ import { PanelLoadingScreen } from "./PanelLoadingScreen";
export interface AddCollectionPanelProps { export interface AddCollectionPanelProps {
explorer: Explorer; explorer: Explorer;
databaseId?: string; databaseId?: string;
isQuickstart?: boolean;
showTeachingBubble?: boolean;
} }
const SharedDatabaseDefault: DataModels.IndexingPolicy = { const SharedDatabaseDefault: DataModels.IndexingPolicy = {
@ -93,6 +97,7 @@ export interface AddCollectionPanelState {
showErrorDetails: boolean; showErrorDetails: boolean;
isExecuting: boolean; isExecuting: boolean;
isThroughputCapExceeded: boolean; isThroughputCapExceeded: boolean;
teachingBubbleStep: number;
} }
export class AddCollectionPanel extends React.Component<AddCollectionPanelProps, AddCollectionPanelState> { export class AddCollectionPanel extends React.Component<AddCollectionPanelProps, AddCollectionPanelState> {
@ -107,11 +112,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
this.state = { this.state = {
createNewDatabase: userContext.apiType !== "Tables" && !this.props.databaseId, createNewDatabase: userContext.apiType !== "Tables" && !this.props.databaseId,
newDatabaseId: "", newDatabaseId: props.isQuickstart ? "SampleDB" : "",
isSharedThroughputChecked: this.getSharedThroughputDefault(), isSharedThroughputChecked: this.getSharedThroughputDefault(),
selectedDatabaseId: selectedDatabaseId:
userContext.apiType === "Tables" ? CollectionCreation.TablesAPIDefaultDatabase : this.props.databaseId, userContext.apiType === "Tables" ? CollectionCreation.TablesAPIDefaultDatabase : this.props.databaseId,
collectionId: "", collectionId: props.isQuickstart ? `Sample${getCollectionName()}` : "",
enableIndexing: true, enableIndexing: true,
isSharded: userContext.apiType !== "Tables", isSharded: userContext.apiType !== "Tables",
partitionKey: this.getPartitionKey(), partitionKey: this.getPartitionKey(),
@ -124,9 +129,16 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
showErrorDetails: false, showErrorDetails: false,
isExecuting: false, isExecuting: false,
isThroughputCapExceeded: false, isThroughputCapExceeded: false,
teachingBubbleStep: 0,
}; };
} }
componentDidMount(): void {
if (this.state.teachingBubbleStep === 0 && this.props.showTeachingBubble) {
this.setState({ teachingBubbleStep: 1 });
}
}
render(): JSX.Element { render(): JSX.Element {
const isFirstResourceCreated = useDatabases.getState().isFirstResourceCreated(); const isFirstResourceCreated = useDatabases.getState().isFirstResourceCreated();
@ -150,6 +162,70 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
/> />
)} )}
{this.state.teachingBubbleStep === 1 && (
<TeachingBubble
headline="Create sample database"
target={"#newDatabaseId"}
calloutProps={{ gapSpace: 16 }}
primaryButtonProps={{ text: "Next", onClick: () => this.setState({ teachingBubbleStep: 2 }) }}
secondaryButtonProps={{ text: "Cancel", onClick: () => this.setState({ teachingBubbleStep: 0 }) }}
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
</TeachingBubble>
)}
{this.state.teachingBubbleStep === 2 && (
<TeachingBubble
headline="Setting throughput"
target={"#autoscaleRUValueField"}
calloutProps={{ gapSpace: 16 }}
primaryButtonProps={{ text: "Next", onClick: () => this.setState({ teachingBubbleStep: 3 }) }}
secondaryButtonProps={{ text: "Previous", onClick: () => this.setState({ teachingBubbleStep: 1 }) }}
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
</TeachingBubble>
)}
{this.state.teachingBubbleStep === 3 && (
<TeachingBubble
headline="Naming container"
target={"#collectionId"}
calloutProps={{ gapSpace: 16 }}
primaryButtonProps={{ text: "Next", onClick: () => this.setState({ teachingBubbleStep: 4 }) }}
secondaryButtonProps={{ text: "Previous", onClick: () => this.setState({ teachingBubbleStep: 2 }) }}
onDismiss={() => this.setState({ teachingBubbleStep: 0 })}
footerContent="Step 3 of 4"
>
Name your container
</TeachingBubble>
)}
{this.state.teachingBubbleStep === 4 && (
<TeachingBubble
headline="Setting partition key"
target={"#addCollection-partitionKeyValue"}
calloutProps={{ gapSpace: 16 }}
primaryButtonProps={{
text: "Create container",
onClick: () => {
this.setState({ teachingBubbleStep: 0 });
this.submit();
},
}}
secondaryButtonProps={{ text: "Previous", onClick: () => this.setState({ teachingBubbleStep: 2 }) }}
onDismiss={() => this.setState({ teachingBubbleStep: 0 })}
footerContent="Step 4 of 4"
>
Last step - you will need to define a partition key for your collection. /address was chosen for this
particular example. A good partition key should have a wide range of possible value
</TeachingBubble>
)}
<div className="panelMainContent"> <div className="panelMainContent">
<Stack hidden={userContext.apiType === "Tables"}> <Stack hidden={userContext.apiType === "Tables"}>
<Stack horizontal> <Stack horizontal>
@ -832,6 +908,9 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
if (userContext.features.partitionKeyDefault2) { if (userContext.features.partitionKeyDefault2) {
return userContext.apiType === "SQL" ? "/pk" : "pk"; return userContext.apiType === "SQL" ? "/pk" : "pk";
} }
if (this.props.isQuickstart) {
return userContext.apiType === "SQL" ? "/address" : "address";
}
return ""; return "";
} }
@ -899,8 +978,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
} }
private isSynapseLinkEnabled(): boolean { private isSynapseLinkEnabled(): boolean {
const { properties } = userContext.databaseAccount; if (!userContext.databaseAccount) {
return false;
}
const { properties } = userContext.databaseAccount;
if (!properties) { if (!properties) {
return false; return false;
} }
@ -996,8 +1078,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
document.getElementById("collapsibleSectionContent")?.scrollIntoView(); document.getElementById("collapsibleSectionContent")?.scrollIntoView();
} }
private async submit(event: React.FormEvent<HTMLFormElement>): Promise<void> { private async submit(event?: React.FormEvent<HTMLFormElement>): Promise<void> {
event.preventDefault(); event?.preventDefault();
if (!this.validateInputs()) { if (!this.validateInputs()) {
return; return;
@ -1090,8 +1172,17 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
try { try {
await createCollection(createCollectionParams); await createCollection(createCollectionParams);
await this.props.explorer.refreshAllDatabases();
if (this.props.isQuickstart) {
const database = useDatabases.getState().findDatabaseWithId("SampleDB");
if (database) {
await database.loadCollections();
const collection = database.findCollectionWithId("SampleContainer");
const sampleGenerator = await ContainerSampleGenerator.createSampleGeneratorAsync(this.props.explorer);
sampleGenerator.populateContainerAsync(collection);
}
}
this.setState({ isExecuting: false }); this.setState({ isExecuting: false });
this.props.explorer.refreshAllDatabases();
TelemetryProcessor.traceSuccess(Action.CreateCollection, telemetryData, startKey); TelemetryProcessor.traceSuccess(Action.CreateCollection, telemetryData, startKey);
useSidePanel.getState().closeSidePanel(); useSidePanel.getState().closeSidePanel();
} catch (error) { } catch (error) {

View File

@ -0,0 +1,427 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AddCollectionPanel should render Default properly 1`] = `
<form
className="panelFormWrapper"
onSubmit={[Function]}
>
<div
className="panelMainContent"
>
<Stack
hidden={false}
>
<Stack
horizontal={true}
>
<span
className="mandatoryStar"
>
* 
</span>
<Text
className="panelTextBold"
variant="small"
>
Database
id
</Text>
<StyledTooltipHostBase
content="A database is analogous to a namespace. It is the unit of management for a set of containers."
directionalHint={4}
>
<Icon
className="panelInfoIcon"
iconName="Info"
tabIndex={0}
/>
</StyledTooltipHostBase>
</Stack>
<Stack
horizontal={true}
verticalAlign="center"
>
<input
aria-checked={true}
aria-label="Create new database"
checked={true}
className="panelRadioBtn"
id="databaseCreateNew"
name="databaseType"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="panelRadioBtnLabel"
>
Create new
</span>
<input
aria-checked={false}
aria-label="Use existing database"
checked={false}
className="panelRadioBtn"
name="databaseType"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="panelRadioBtnLabel"
>
Use existing
</span>
</Stack>
<Stack
className="panelGroupSpacing"
>
<input
aria-label="New database id"
aria-required={true}
autoComplete="off"
autoFocus={true}
className="panelTextField"
id="newDatabaseId"
name="newDatabaseId"
onChange={[Function]}
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
placeholder="Type a new database id"
required={true}
size={40}
tabIndex={0}
title="May not end with space nor contain characters '\\\\' '/' '#' '?'"
type="text"
value=""
/>
<Stack
horizontal={true}
>
<StyledCheckboxBase
checked={true}
label="Share throughput across containers"
onChange={[Function]}
styles={
Object {
"checkbox": Object {
"height": 12,
"width": 12,
},
"label": Object {
"alignItems": "center",
"padding": 0,
},
"text": Object {
"fontSize": 12,
},
}
}
/>
<StyledTooltipHostBase
content="Throughput configured at the database level will be shared across all containers within the database."
directionalHint={4}
>
<Icon
className="panelInfoIcon"
iconName="Info"
tabIndex={0}
/>
</StyledTooltipHostBase>
</Stack>
<ThroughputInput
isDatabase={true}
isSharded={true}
onCostAcknowledgeChange={[Function]}
setIsAutoscale={[Function]}
setIsThroughputCapExceeded={[Function]}
setThroughputValue={[Function]}
/>
</Stack>
<Separator
className="panelSeparator"
/>
</Stack>
<Stack>
<Stack
horizontal={true}
>
<span
className="mandatoryStar"
>
* 
</span>
<Text
className="panelTextBold"
variant="small"
>
Container id
</Text>
<StyledTooltipHostBase
content="Unique identifier for the container and used for id-based routing through REST and all SDKs."
directionalHint={4}
>
<Icon
className="panelInfoIcon"
iconName="Info"
tabIndex={0}
/>
</StyledTooltipHostBase>
</Stack>
<input
aria-label="Container id"
aria-required={true}
autoComplete="off"
className="panelTextField"
id="collectionId"
name="collectionId"
onChange={[Function]}
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
placeholder="e.g., Container1"
required={true}
size={40}
title="May not end with space nor contain characters '\\\\' '/' '#' '?'"
type="text"
value=""
/>
</Stack>
<Stack>
<Stack
horizontal={true}
>
<span
className="mandatoryStar"
>
* 
</span>
<Text
className="panelTextBold"
variant="small"
>
Partition key
</Text>
<StyledTooltipHostBase
content="The partition key is used to automatically distribute data across partitions for scalability. Choose a property in your JSON document that has a wide range of values and evenly distributes request volume. For small read-heavy workloads or write-heavy workloads of any size, id is often a good choice."
directionalHint={4}
>
<Icon
className="panelInfoIcon"
iconName="Info"
tabIndex={0}
/>
</StyledTooltipHostBase>
</Stack>
<Text
aria-label="pkDescription"
variant="small"
/>
<input
aria-label="Partition key"
aria-required={true}
className="panelTextField"
id="addCollection-partitionKeyValue"
onChange={[Function]}
pattern=".*"
placeholder="e.g., /address/zipCode"
required={true}
size={40}
title=""
type="text"
value=""
/>
</Stack>
<Stack>
<Stack
horizontal={true}
>
<Text
className="panelTextBold"
variant="small"
>
Unique keys
</Text>
<StyledTooltipHostBase
content="Unique keys provide developers with the ability to add a layer of data integrity to their database. By creating a unique key policy when a container is created, you ensure the uniqueness of one or more values per partition key."
directionalHint={4}
>
<Icon
className="panelInfoIcon"
iconName="Info"
tabIndex={0}
/>
</StyledTooltipHostBase>
</Stack>
<CustomizedActionButton
iconProps={
Object {
"iconName": "Add",
}
}
onClick={[Function]}
styles={
Object {
"label": Object {
"fontSize": 12,
},
"root": Object {
"padding": 0,
},
}
}
>
Add unique key
</CustomizedActionButton>
</Stack>
<Stack
className="panelGroupSpacing"
>
<Stack
horizontal={true}
>
<Text
className="panelTextBold"
variant="small"
>
Analytical store
</Text>
<StyledTooltipHostBase
content={
<Text
variant="small"
>
Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads.
<StyledLinkBase
href="https://aka.ms/analytical-store-overview"
target="_blank"
>
Learn more
</StyledLinkBase>
</Text>
}
directionalHint={4}
>
<Icon
className="panelInfoIcon"
iconName="Info"
tabIndex={0}
/>
</StyledTooltipHostBase>
</Stack>
<Stack
horizontal={true}
verticalAlign="center"
>
<input
aria-checked={false}
aria-label="Enable analytical store"
checked={false}
className="panelRadioBtn"
disabled={true}
id="enableAnalyticalStoreBtn"
name="analyticalStore"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="panelRadioBtnLabel"
>
On
</span>
<input
aria-checked={true}
aria-label="Disable analytical store"
checked={true}
className="panelRadioBtn"
disabled={true}
id="disableAnalyticalStoreBtn"
name="analyticalStore"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="panelRadioBtnLabel"
>
Off
</span>
</Stack>
<Stack
className="panelGroupSpacing"
>
<Text
variant="small"
>
Azure Synapse Link is required for creating an analytical store
container
. Enable Synapse Link for this Cosmos DB account.
<StyledLinkBase
href="https://aka.ms/cosmosdb-synapselink"
target="_blank"
>
Learn more
</StyledLinkBase>
</Text>
<CustomizedDefaultButton
onClick={[Function]}
style={
Object {
"height": 27,
"width": 80,
}
}
styles={
Object {
"label": Object {
"fontSize": 12,
},
}
}
text="Enable"
/>
</Stack>
</Stack>
<CollapsibleSectionComponent
isExpandedByDefault={false}
onExpand={[Function]}
title="Advanced"
>
<Stack
className="panelGroupSpacing"
id="collapsibleSectionContent"
>
<StyledCheckboxBase
checked={false}
label="My partition key is larger than 101 bytes"
onChange={[Function]}
styles={
Object {
"checkbox": Object {
"height": 12,
"width": 12,
},
"label": Object {
"alignItems": "center",
"padding": 0,
},
"text": Object {
"fontSize": 12,
},
}
}
/>
</Stack>
</CollapsibleSectionComponent>
</div>
<PanelFooterComponent
buttonLabel="OK"
isButtonDisabled={false}
/>
</form>
`;

View File

@ -1,7 +1,7 @@
/** /**
* Accordion top class * Accordion top class
*/ */
import { Image, Link, Stack, Text } from "@fluentui/react"; import { Coachmark, DirectionalHint, Image, Link, Stack, TeachingBubbleContent, Text } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import AddDatabaseIcon from "../../../images/AddDatabase.svg"; import AddDatabaseIcon from "../../../images/AddDatabase.svg";
import NewQueryIcon from "../../../images/AddSqlQuery_16x16.svg"; import NewQueryIcon from "../../../images/AddSqlQuery_16x16.svg";
@ -39,6 +39,7 @@ import { useSelectedNode } from "../useSelectedNode";
export interface SplashScreenItem { export interface SplashScreenItem {
iconSrc: string; iconSrc: string;
title: string; title: string;
id?: string;
info?: string; info?: string;
description: string; description: string;
onClick: () => void; onClick: () => void;
@ -48,7 +49,11 @@ export interface SplashScreenProps {
explorer: Explorer; explorer: Explorer;
} }
export class SplashScreen extends React.Component<SplashScreenProps> { export interface SplashScreenState {
showCoachmark: boolean;
}
export class SplashScreen extends React.Component<SplashScreenProps, SplashScreenState> {
private static readonly seeMoreItemTitle: string = "See more Cosmos DB documentation"; private static readonly seeMoreItemTitle: string = "See more Cosmos DB documentation";
private static readonly seeMoreItemUrl: string = "https://aka.ms/cosmosdbdocument"; private static readonly seeMoreItemUrl: string = "https://aka.ms/cosmosdbdocument";
private static readonly dataModelingUrl = "https://docs.microsoft.com/azure/cosmos-db/modeling-data"; private static readonly dataModelingUrl = "https://docs.microsoft.com/azure/cosmos-db/modeling-data";
@ -62,15 +67,19 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
super(props); super(props);
this.container = props.explorer; this.container = props.explorer;
this.subscriptions = []; this.subscriptions = [];
this.state = {
showCoachmark: userContext.features.enableNewQuickstart,
};
} }
public componentWillUnmount() { public componentWillUnmount(): void {
while (this.subscriptions.length) { while (this.subscriptions.length) {
this.subscriptions.pop().dispose(); this.subscriptions.pop().dispose();
} }
} }
public componentDidMount() { public componentDidMount(): void {
this.subscriptions.push( this.subscriptions.push(
{ {
dispose: useNotebook.subscribe( dispose: useNotebook.subscribe(
@ -116,13 +125,41 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
</div> </div>
<div className="legendContainer"> <div className="legendContainer">
<div className="legend">{item.title}</div> <div className="legend">{item.title}</div>
<div className={userContext.features.enableNewQuickstart ? "newDescription" : "description"}> <div
id={item.id}
className={userContext.features.enableNewQuickstart ? "newDescription" : "description"}
>
{item.description} {item.description}
</div> </div>
</div> </div>
</Stack> </Stack>
))} ))}
</div> </div>
{this.state.showCoachmark && (
<Coachmark
target="#quickstartDescription"
positioningContainerProps={{ directionalHint: DirectionalHint.rightTopEdge }}
persistentBeak
>
<TeachingBubbleContent
headline={`Start with sample ${getCollectionName().toLocaleLowerCase()}`}
hasCloseButton
closeButtonAriaLabel="Close"
primaryButtonProps={{
text: "Get started",
onClick: () => {
this.setState({ showCoachmark: false });
this.container.onNewCollectionClicked({ isQuickstart: true, showTeachingBubble: true });
},
}}
secondaryButtonProps={{ text: "Cancel", onClick: () => this.setState({ showCoachmark: false }) }}
onDismiss={() => this.setState({ showCoachmark: 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
</TeachingBubbleContent>
</Coachmark>
)}
<div className="moreStuffContainer"> <div className="moreStuffContainer">
<div className="moreStuffColumn commonTasks"> <div className="moreStuffColumn commonTasks">
<div className="title"> <div className="title">
@ -165,11 +202,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
if (userContext.features.enableNewQuickstart) { if (userContext.features.enableNewQuickstart) {
const launchQuickstartBtn = { const launchQuickstartBtn = {
id: "quickstartDescription",
iconSrc: QuickStartIcon, iconSrc: QuickStartIcon,
title: "Launch quick start", title: "Launch quick start",
description: "Launch a quick start tutorial to get started with sample data", description: "Launch a quick start tutorial to get started with sample data",
// TODO: replace onClick function onClick: () => this.container.onNewCollectionClicked({ isQuickstart: true }),
onClick: () => 1,
}; };
const newContainerBtn = { const newContainerBtn = {

View File

@ -11,7 +11,7 @@ const fileToUpload = `GettingStarted-ignore${Math.floor(Math.random() * 100000)}
fs.copyFileSync(path.join(__dirname, filename), path.join(__dirname, fileToUpload)); fs.copyFileSync(path.join(__dirname, filename), path.join(__dirname, fileToUpload));
test("Notebooks", async () => { test("Notebooks", async () => {
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner"); await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner-west-us");
const explorer = await waitForExplorer(); const explorer = await waitForExplorer();
// Upload and Delete Notebook // Upload and Delete Notebook
await explorer.click('[data-test="My Notebooks"] [aria-label="More"]'); await explorer.click('[data-test="My Notebooks"] [aria-label="More"]');

View File

@ -9,7 +9,7 @@ test("SQL CRUD", async () => {
const containerId = generateUniqueName("container"); const containerId = generateUniqueName("container");
page.setDefaultTimeout(50000); page.setDefaultTimeout(50000);
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner"); await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner-west-us");
const explorer = await waitForExplorer(); const explorer = await waitForExplorer();
await explorer.click('[data-test="New Container"]'); await explorer.click('[data-test="New Container"]');
await explorer.fill('[aria-label="New database id"]', databaseId); await explorer.fill('[aria-label="New database id"]', databaseId);

View File

@ -15,8 +15,8 @@ const resourceGroupName = "runners";
test("Resource token", async () => { test("Resource token", async () => {
const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId); const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId);
const armClient = new CosmosDBManagementClient(credentials, subscriptionId); const armClient = new CosmosDBManagementClient(credentials, subscriptionId);
const account = await armClient.databaseAccounts.get(resourceGroupName, "portal-sql-runner"); const account = await armClient.databaseAccounts.get(resourceGroupName, "portal-sql-runner-west-us");
const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, "portal-sql-runner"); const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, "portal-sql-runner-west-us");
const dbId = generateUniqueName("db"); const dbId = generateUniqueName("db");
const collectionId = generateUniqueName("col"); const collectionId = generateUniqueName("col");
const client = new CosmosClient({ const client = new CosmosClient({

View File

@ -8,7 +8,7 @@ import { get, listKeys } from "../../src/Utils/arm/generatedClients/cosmos/datab
const resourceGroup = process.env.RESOURCE_GROUP || ""; const resourceGroup = process.env.RESOURCE_GROUP || "";
const subscriptionId = process.env.SUBSCRIPTION_ID || ""; const subscriptionId = process.env.SUBSCRIPTION_ID || "";
const urlSearchParams = new URLSearchParams(window.location.search); const urlSearchParams = new URLSearchParams(window.location.search);
const accountName = urlSearchParams.get("accountName") || "portal-sql-runner"; const accountName = urlSearchParams.get("accountName") || "portal-sql-runner-west-us";
const selfServeType = urlSearchParams.get("selfServeType") || "example"; const selfServeType = urlSearchParams.get("selfServeType") || "example";
const iframeSrc = urlSearchParams.get("iframeSrc") || "explorer.html?platform=Portal&disablePortalInitCache"; const iframeSrc = urlSearchParams.get("iframeSrc") || "explorer.html?platform=Portal&disablePortalInitCache";