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:
parent
d05a05716f
commit
ebbfc5f517
|
@ -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()}`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -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 },
|
||||||
|
|
|
@ -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]}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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} />);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
|
@ -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) {
|
||||||
|
|
|
@ -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>
|
||||||
|
`;
|
|
@ -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 = {
|
||||||
|
|
|
@ -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"]');
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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";
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue