mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-29 08:56:52 +00:00
Move add collection pane to React (#486)
* Move add collection pane to React * Add feature flag * fix unit tests * FIx merge conflicts and address comments * Resolve merge conflicts * Address comments * Fix e2e test failure * Update test snapshots * Update test snapshots
This commit is contained in:
parent
c6090e2663
commit
65c859c835
@ -120,6 +120,7 @@ export class Features {
|
|||||||
public static readonly enableDatabaseSettingsTabV1 = "enabledbsettingsv1";
|
public static readonly enableDatabaseSettingsTabV1 = "enabledbsettingsv1";
|
||||||
public static readonly selfServeType = "selfservetype";
|
public static readonly selfServeType = "selfservetype";
|
||||||
public static readonly enableKOPanel = "enablekopanel";
|
public static readonly enableKOPanel = "enablekopanel";
|
||||||
|
public static readonly enableReactPane = "enablereactpane";
|
||||||
}
|
}
|
||||||
|
|
||||||
// flight names returned from the portal are always lowercase
|
// flight names returned from the portal are always lowercase
|
||||||
|
@ -6,6 +6,7 @@ describe("CollapsibleSectionComponent", () => {
|
|||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
const props: CollapsibleSectionProps = {
|
const props: CollapsibleSectionProps = {
|
||||||
title: "Sample title",
|
title: "Sample title",
|
||||||
|
isExpandedByDefault: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<CollapsibleSectionComponent {...props} />);
|
const wrapper = shallow(<CollapsibleSectionComponent {...props} />);
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { Icon, Label, Stack } from "office-ui-fabric-react";
|
import { Icon, Label, Stack } from "office-ui-fabric-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { accordionIconStyles, accordionStackTokens } from "../Settings/SettingsRenderUtils";
|
import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
|
||||||
|
|
||||||
export interface CollapsibleSectionProps {
|
export interface CollapsibleSectionProps {
|
||||||
title: string;
|
title: string;
|
||||||
|
isExpandedByDefault: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollapsibleSectionState {
|
export interface CollapsibleSectionState {
|
||||||
@ -14,7 +15,7 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
|
|||||||
constructor(props: CollapsibleSectionProps) {
|
constructor(props: CollapsibleSectionProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
isExpanded: true,
|
isExpanded: this.props.isExpandedByDefault,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,8 +26,14 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
|
|||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack className="collapsibleSection" horizontal tokens={accordionStackTokens} onClick={this.toggleCollapsed}>
|
<Stack
|
||||||
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} styles={accordionIconStyles} />
|
className="collapsibleSection"
|
||||||
|
horizontal
|
||||||
|
verticalAlign="center"
|
||||||
|
tokens={accordionStackTokens}
|
||||||
|
onClick={this.toggleCollapsed}
|
||||||
|
>
|
||||||
|
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} />
|
||||||
<Label>{this.props.title}</Label>
|
<Label>{this.props.title}</Label>
|
||||||
</Stack>
|
</Stack>
|
||||||
{this.state.isExpanded && this.props.children}
|
{this.state.isExpanded && this.props.children}
|
||||||
|
@ -11,16 +11,10 @@ exports[`CollapsibleSectionComponent renders 1`] = `
|
|||||||
"childrenGap": 10,
|
"childrenGap": 10,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
verticalAlign="center"
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
iconName="ChevronDown"
|
iconName="ChevronDown"
|
||||||
styles={
|
|
||||||
Object {
|
|
||||||
"root": Object {
|
|
||||||
"paddingTop": 7,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<StyledLabelBase>
|
<StyledLabelBase>
|
||||||
Sample title
|
Sample title
|
||||||
|
@ -23,7 +23,6 @@ import {
|
|||||||
ITextStyles,
|
ITextStyles,
|
||||||
IDetailsRowStyles,
|
IDetailsRowStyles,
|
||||||
IStackStyles,
|
IStackStyles,
|
||||||
IIconStyles,
|
|
||||||
IDetailsListStyles,
|
IDetailsListStyles,
|
||||||
IDropdownStyles,
|
IDropdownStyles,
|
||||||
ISeparatorStyles,
|
ISeparatorStyles,
|
||||||
@ -116,8 +115,6 @@ export const addMongoIndexSubElementsTokens: IStackTokens = {
|
|||||||
childrenGap: 20,
|
childrenGap: 20,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const accordionIconStyles: IIconStyles = { root: { paddingTop: 7 } };
|
|
||||||
|
|
||||||
export const mediumWidthStackStyles: IStackStyles = { root: { width: 600 } };
|
export const mediumWidthStackStyles: IStackStyles = { root: { width: 600 } };
|
||||||
|
|
||||||
export const shortWidthTextFieldStyles: Partial<ITextFieldStyles> = { root: { paddingLeft: 10, width: 210 } };
|
export const shortWidthTextFieldStyles: Partial<ITextFieldStyles> = { root: { paddingLeft: 10, width: 210 } };
|
||||||
|
@ -239,7 +239,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack {...createAndAddMongoIndexStackProps} styles={mediumWidthStackStyles}>
|
<Stack {...createAndAddMongoIndexStackProps} styles={mediumWidthStackStyles}>
|
||||||
<CollapsibleSectionComponent title="Current index(es)">
|
<CollapsibleSectionComponent title="Current index(es)" isExpandedByDefault={true}>
|
||||||
{
|
{
|
||||||
<>
|
<>
|
||||||
<DetailsList
|
<DetailsList
|
||||||
@ -266,7 +266,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack styles={mediumWidthStackStyles}>
|
<Stack styles={mediumWidthStackStyles}>
|
||||||
<CollapsibleSectionComponent title="Index(es) to be dropped">
|
<CollapsibleSectionComponent title="Index(es) to be dropped" isExpandedByDefault={true}>
|
||||||
{indexesToBeDropped.length > 0 && (
|
{indexesToBeDropped.length > 0 && (
|
||||||
<DetailsList
|
<DetailsList
|
||||||
styles={customDetailsListStyles}
|
styles={customDetailsListStyles}
|
||||||
|
@ -42,6 +42,7 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<CollapsibleSectionComponent
|
<CollapsibleSectionComponent
|
||||||
|
isExpandedByDefault={true}
|
||||||
title="Current index(es)"
|
title="Current index(es)"
|
||||||
>
|
>
|
||||||
<StyledWithViewportComponent
|
<StyledWithViewportComponent
|
||||||
@ -139,6 +140,7 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<CollapsibleSectionComponent
|
<CollapsibleSectionComponent
|
||||||
|
isExpandedByDefault={true}
|
||||||
title="Index(es) to be dropped"
|
title="Index(es) to be dropped"
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
20
src/Explorer/Controls/ThroughputInput/ThroughputInput.less
Normal file
20
src/Explorer/Controls/ThroughputInput/ThroughputInput.less
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
@import "../../../../less/Common/Constants";
|
||||||
|
|
||||||
|
.throughputInputContainer {
|
||||||
|
.throughputInputRadioBtn {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.throughputInputRadioBtnLabel {
|
||||||
|
font-size: @mediumFontSize;
|
||||||
|
padding: 0 @LargeSpace 0 @SmallSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.throughputInputSpacing {
|
||||||
|
margin-bottom: @SmallSpace;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
margin-bottom: @SmallSpace;
|
||||||
|
}
|
||||||
|
}
|
302
src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx
Normal file
302
src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
import { Checkbox, DirectionalHint, Icon, Link, Stack, Text, TextField, TooltipHost } from "office-ui-fabric-react";
|
||||||
|
import React from "react";
|
||||||
|
import * as Constants from "../../../Common/Constants";
|
||||||
|
import * as SharedConstants from "../../../Shared/Constants";
|
||||||
|
import { userContext } from "../../../UserContext";
|
||||||
|
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||||
|
import * as PricingUtils from "../../../Utils/PricingUtils";
|
||||||
|
|
||||||
|
export interface ThroughputInputProps {
|
||||||
|
isDatabase: boolean;
|
||||||
|
showFreeTierExceedThroughputTooltip: boolean;
|
||||||
|
setThroughputValue: (throughput: number) => void;
|
||||||
|
setIsAutoscale: (isAutoscale: boolean) => void;
|
||||||
|
onCostAcknowledgeChange: (isAcknowledged: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ThroughputInputState {
|
||||||
|
isAutoscaleSelected: boolean;
|
||||||
|
throughput: number;
|
||||||
|
isCostAcknowledged: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ThroughputInput extends React.Component<ThroughputInputProps, ThroughputInputState> {
|
||||||
|
constructor(props: ThroughputInputProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isAutoscaleSelected: true,
|
||||||
|
throughput: AutoPilotUtils.minAutoPilotThroughput,
|
||||||
|
isCostAcknowledged: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.props.setThroughputValue(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
|
this.props.setIsAutoscale(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<div className="throughputInputContainer throughputInputSpacing">
|
||||||
|
<Stack horizontal>
|
||||||
|
<span className="mandatoryStar">* </span>
|
||||||
|
<Text variant="small" style={{ lineHeight: "20px" }}>
|
||||||
|
{this.getThroughputLabelText()}
|
||||||
|
</Text>
|
||||||
|
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={PricingUtils.getRuToolTipText()}>
|
||||||
|
<Icon iconName="InfoSolid" className="panelInfoIcon" />
|
||||||
|
</TooltipHost>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack horizontal verticalAlign="center">
|
||||||
|
<input
|
||||||
|
className="throughputInputRadioBtn"
|
||||||
|
aria-label="Autoscale mode"
|
||||||
|
checked={this.state.isAutoscaleSelected}
|
||||||
|
type="radio"
|
||||||
|
role="radio"
|
||||||
|
tabIndex={0}
|
||||||
|
onChange={this.onAutoscaleRadioBtnChange.bind(this)}
|
||||||
|
/>
|
||||||
|
<span className="throughputInputRadioBtnLabel">Autoscale</span>
|
||||||
|
|
||||||
|
<input
|
||||||
|
className="throughputInputRadioBtn"
|
||||||
|
aria-label="Manual mode"
|
||||||
|
checked={!this.state.isAutoscaleSelected}
|
||||||
|
type="radio"
|
||||||
|
role="radio"
|
||||||
|
tabIndex={0}
|
||||||
|
onChange={this.onManualRadioBtnChange.bind(this)}
|
||||||
|
/>
|
||||||
|
<span className="throughputInputRadioBtnLabel">Manual</span>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{this.state.isAutoscaleSelected && (
|
||||||
|
<Stack className="throughputInputSpacing">
|
||||||
|
<Text variant="small">
|
||||||
|
Provision maximum RU/s required by this resource. Estimate your required RU/s with
|
||||||
|
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/">
|
||||||
|
capacity calculator
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Stack horizontal>
|
||||||
|
<Text variant="small" style={{ lineHeight: "20px" }}>
|
||||||
|
Max RU/s
|
||||||
|
</Text>
|
||||||
|
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={this.getAutoScaleTooltip()}>
|
||||||
|
<Icon iconName="InfoSolid" className="panelInfoIcon" />
|
||||||
|
</TooltipHost>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
type="number"
|
||||||
|
styles={{
|
||||||
|
fieldGroup: { width: 300, height: 27 },
|
||||||
|
field: { fontSize: 12 },
|
||||||
|
}}
|
||||||
|
onChange={(event, newInput?: string) => this.onThroughputValueChange(newInput)}
|
||||||
|
step={AutoPilotUtils.autoPilotIncrementStep}
|
||||||
|
min={AutoPilotUtils.minAutoPilotThroughput}
|
||||||
|
value={this.state.throughput.toString()}
|
||||||
|
aria-label="Max request units per second"
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Text variant="small">
|
||||||
|
Your {this.props.isDatabase ? "database" : "container"} throughput will automatically scale from{" "}
|
||||||
|
<b>
|
||||||
|
{AutoPilotUtils.getMinRUsBasedOnUserInput(this.state.throughput)} RU/s (10% of max RU/s) -{" "}
|
||||||
|
{this.state.throughput} RU/s
|
||||||
|
</b>{" "}
|
||||||
|
based on usage.
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!this.state.isAutoscaleSelected && (
|
||||||
|
<Stack className="throughputInputSpacing">
|
||||||
|
<Text variant="small">
|
||||||
|
Estimate your required RU/s with
|
||||||
|
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/">
|
||||||
|
capacity calculator
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<TooltipHost
|
||||||
|
directionalHint={DirectionalHint.topLeftEdge}
|
||||||
|
content={
|
||||||
|
this.props.showFreeTierExceedThroughputTooltip &&
|
||||||
|
this.state.throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs400
|
||||||
|
? "The first 400 RU/s in this account are free. Billing will apply to any throughput beyond 400 RU/s."
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
type="number"
|
||||||
|
styles={{
|
||||||
|
fieldGroup: { width: 300, height: 27 },
|
||||||
|
field: { fontSize: 12 },
|
||||||
|
}}
|
||||||
|
onChange={(event, newInput?: string) => this.onThroughputValueChange(newInput)}
|
||||||
|
step={100}
|
||||||
|
min={SharedConstants.CollectionCreation.DefaultCollectionRUs400}
|
||||||
|
max={userContext.isTryCosmosDBSubscription ? Constants.TryCosmosExperience.maxRU : Infinity}
|
||||||
|
value={this.state.throughput.toString()}
|
||||||
|
aria-label="Max request units per second"
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
</TooltipHost>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<CostEstimateText requestUnits={this.state.throughput} isAutoscale={this.state.isAutoscaleSelected} />
|
||||||
|
|
||||||
|
{this.state.throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && (
|
||||||
|
<Stack horizontal verticalAlign="start">
|
||||||
|
<Checkbox
|
||||||
|
checked={this.state.isCostAcknowledged}
|
||||||
|
styles={{
|
||||||
|
checkbox: { width: 12, height: 12 },
|
||||||
|
label: { padding: 0, margin: "4px 4px 0 0" },
|
||||||
|
}}
|
||||||
|
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) => {
|
||||||
|
this.setState({ isCostAcknowledged: isChecked });
|
||||||
|
this.props.onCostAcknowledgeChange(isChecked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text variant="small" style={{ lineHeight: "20px" }}>
|
||||||
|
{this.getCostAcknowledgeText()}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getThroughputLabelText(): string {
|
||||||
|
if (this.state.isAutoscaleSelected) {
|
||||||
|
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||||
|
}
|
||||||
|
|
||||||
|
const minRU: string = SharedConstants.CollectionCreation.DefaultCollectionRUs400.toLocaleString();
|
||||||
|
const maxRU: string = userContext.isTryCosmosDBSubscription
|
||||||
|
? Constants.TryCosmosExperience.maxRU.toLocaleString()
|
||||||
|
: "unlimited";
|
||||||
|
return this.state.isAutoscaleSelected
|
||||||
|
? AutoPilotUtils.getAutoPilotHeaderText()
|
||||||
|
: `Throughput (${minRU} - ${maxRU} RU/s)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private onThroughputValueChange(newInput: string): void {
|
||||||
|
const newThroughput = parseInt(newInput);
|
||||||
|
this.setState({ throughput: newThroughput });
|
||||||
|
this.props.setThroughputValue(newThroughput);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAutoScaleTooltip(): string {
|
||||||
|
return `After the first ${AutoPilotUtils.getStorageBasedOnUserInput(
|
||||||
|
this.state.throughput
|
||||||
|
)} GB of data stored, the max
|
||||||
|
RU/s will be automatically upgraded based on the new storage value.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCostAcknowledgeText(): string {
|
||||||
|
const databaseAccount = userContext.databaseAccount;
|
||||||
|
if (!databaseAccount || !databaseAccount.properties) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const numberOfRegions: number = databaseAccount.properties.readLocations?.length || 1;
|
||||||
|
const multimasterEnabled: boolean = databaseAccount.properties.enableMultipleWriteLocations;
|
||||||
|
|
||||||
|
return PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||||
|
this.state.throughput,
|
||||||
|
userContext.portalEnv,
|
||||||
|
numberOfRegions,
|
||||||
|
multimasterEnabled,
|
||||||
|
this.state.isAutoscaleSelected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onAutoscaleRadioBtnChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||||
|
if (event.target.checked && !this.state.isAutoscaleSelected) {
|
||||||
|
this.setState({ isAutoscaleSelected: true, throughput: AutoPilotUtils.minAutoPilotThroughput });
|
||||||
|
this.props.setIsAutoscale(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onManualRadioBtnChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||||
|
if (event.target.checked && this.state.isAutoscaleSelected) {
|
||||||
|
this.setState({
|
||||||
|
isAutoscaleSelected: false,
|
||||||
|
throughput: SharedConstants.CollectionCreation.DefaultCollectionRUs400,
|
||||||
|
});
|
||||||
|
this.props.setIsAutoscale(false);
|
||||||
|
this.props.setThroughputValue(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CostEstimateTextProps {
|
||||||
|
requestUnits: number;
|
||||||
|
isAutoscale: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CostEstimateText: React.FunctionComponent<CostEstimateTextProps> = (props: CostEstimateTextProps) => {
|
||||||
|
const { requestUnits, isAutoscale } = props;
|
||||||
|
const databaseAccount = userContext.databaseAccount;
|
||||||
|
if (!databaseAccount || !databaseAccount.properties) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serverId: string = userContext.portalEnv;
|
||||||
|
const numberOfRegions: number = databaseAccount.properties.readLocations?.length || 1;
|
||||||
|
const multimasterEnabled: boolean = databaseAccount.properties.enableMultipleWriteLocations;
|
||||||
|
const hourlyPrice: number = PricingUtils.computeRUUsagePriceHourly({
|
||||||
|
serverId,
|
||||||
|
requestUnits,
|
||||||
|
numberOfRegions,
|
||||||
|
multimasterEnabled,
|
||||||
|
isAutoscale,
|
||||||
|
});
|
||||||
|
const dailyPrice: number = hourlyPrice * 24;
|
||||||
|
const monthlyPrice: number = hourlyPrice * SharedConstants.hoursInAMonth;
|
||||||
|
const currency: string = PricingUtils.getPriceCurrency(serverId);
|
||||||
|
const currencySign: string = PricingUtils.getCurrencySign(serverId);
|
||||||
|
const multiplier = PricingUtils.getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
||||||
|
const pricePerRu = isAutoscale
|
||||||
|
? PricingUtils.getAutoscalePricePerRu(serverId, multiplier) * multiplier
|
||||||
|
: PricingUtils.getPricePerRu(serverId) * multiplier;
|
||||||
|
|
||||||
|
if (isAutoscale) {
|
||||||
|
return (
|
||||||
|
<Text variant="small">
|
||||||
|
Estimated monthly cost ({currency}):{" "}
|
||||||
|
<b>
|
||||||
|
{currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice / 10)} -{" "}
|
||||||
|
{currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice)}{" "}
|
||||||
|
</b>
|
||||||
|
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits / 10} - {requestUnits}{" "}
|
||||||
|
RU/s, {currencySign + pricePerRu}/RU)
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text variant="small">
|
||||||
|
Cost ({currency}):{" "}
|
||||||
|
<b>
|
||||||
|
{currencySign + PricingUtils.calculateEstimateNumber(hourlyPrice)} hourly /{" "}
|
||||||
|
{currencySign + PricingUtils.calculateEstimateNumber(dailyPrice)} daily /{" "}
|
||||||
|
{currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice)} monthly{" "}
|
||||||
|
</b>
|
||||||
|
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits}RU/s,{" "}
|
||||||
|
{currencySign + pricePerRu}/RU)
|
||||||
|
<br />
|
||||||
|
<em>{PricingUtils.estimatedCostDisclaimer}</em>
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
@ -48,6 +48,7 @@ import { FileSystemUtil } from "./Notebook/FileSystemUtil";
|
|||||||
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
|
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
|
||||||
import { NotebookUtil } from "./Notebook/NotebookUtil";
|
import { NotebookUtil } from "./Notebook/NotebookUtil";
|
||||||
import AddCollectionPane from "./Panes/AddCollectionPane";
|
import AddCollectionPane from "./Panes/AddCollectionPane";
|
||||||
|
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
|
||||||
import AddDatabasePane from "./Panes/AddDatabasePane";
|
import AddDatabasePane from "./Panes/AddDatabasePane";
|
||||||
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane";
|
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane";
|
||||||
import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
|
import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
|
||||||
@ -2392,11 +2393,13 @@ export default class Explorer {
|
|||||||
public onNewCollectionClicked(): void {
|
public onNewCollectionClicked(): void {
|
||||||
if (this.isPreferredApiCassandra()) {
|
if (this.isPreferredApiCassandra()) {
|
||||||
this.cassandraAddCollectionPane.open();
|
this.cassandraAddCollectionPane.open();
|
||||||
|
} else if (this.isFeatureEnabled(Constants.Features.enableReactPane)) {
|
||||||
|
this.openAddCollectionPanel();
|
||||||
} else {
|
} else {
|
||||||
this.addCollectionPane.open(this.selectedDatabaseId());
|
this.addCollectionPane.open(this.selectedDatabaseId());
|
||||||
}
|
|
||||||
document.getElementById("linkAddCollection").focus();
|
document.getElementById("linkAddCollection").focus();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private refreshCommandBarButtons(): void {
|
private refreshCommandBarButtons(): void {
|
||||||
const activeTab = this.tabsManager.activeTab();
|
const activeTab = this.tabsManager.activeTab();
|
||||||
@ -2535,4 +2538,16 @@ export default class Explorer {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async openAddCollectionPanel(): Promise<void> {
|
||||||
|
await this.loadDatabaseOffers();
|
||||||
|
this.openSidePanel(
|
||||||
|
"New Collection",
|
||||||
|
<AddCollectionPanel
|
||||||
|
explorer={this}
|
||||||
|
closePanel={() => this.closeSidePanel()}
|
||||||
|
openNotificationConsole={() => this.expandConsole()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
1018
src/Explorer/Panes/AddCollectionPanel.tsx
Normal file
1018
src/Explorer/Panes/AddCollectionPanel.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@ -133,7 +133,7 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
.simulate("change", { target: { value: selectedCollectionId } });
|
.simulate("change", { target: { value: selectedCollectionId } });
|
||||||
|
|
||||||
expect(wrapper.exists("#sidePanelOkButton")).toBe(true);
|
expect(wrapper.exists("#sidePanelOkButton")).toBe(true);
|
||||||
wrapper.find("#sidePanelOkButton").hostNodes().simulate("click");
|
wrapper.find("#sidePanelOkButton").hostNodes().simulate("submit");
|
||||||
expect(deleteCollection).toHaveBeenCalledWith(databaseId, selectedCollectionId);
|
expect(deleteCollection).toHaveBeenCalledWith(databaseId, selectedCollectionId);
|
||||||
|
|
||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
@ -154,7 +154,7 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
.simulate("change", { target: { value: feedbackText } });
|
.simulate("change", { target: { value: feedbackText } });
|
||||||
|
|
||||||
expect(wrapper.exists("#sidePanelOkButton")).toBe(true);
|
expect(wrapper.exists("#sidePanelOkButton")).toBe(true);
|
||||||
wrapper.find("#sidePanelOkButton").hostNodes().simulate("click");
|
wrapper.find("#sidePanelOkButton").hostNodes().simulate("submit");
|
||||||
expect(deleteCollection).toHaveBeenCalledWith(databaseId, selectedCollectionId);
|
expect(deleteCollection).toHaveBeenCalledWith(databaseId, selectedCollectionId);
|
||||||
|
|
||||||
const deleteFeedback = new DeleteFeedback(
|
const deleteFeedback = new DeleteFeedback(
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import * as React from "react";
|
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
import { PanelFooterComponent } from "./PanelFooterComponent";
|
|
||||||
import { Collection } from "../../Contracts/ViewModels";
|
|
||||||
import { Text, TextField } from "office-ui-fabric-react";
|
import { Text, TextField } from "office-ui-fabric-react";
|
||||||
import { userContext } from "../../UserContext";
|
import * as React from "react";
|
||||||
import { Areas } from "../../Common/Constants";
|
import { Areas } from "../../Common/Constants";
|
||||||
import { deleteCollection } from "../../Common/dataAccess/deleteCollection";
|
import { deleteCollection } from "../../Common/dataAccess/deleteCollection";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
|
||||||
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
|
||||||
import { PanelErrorComponent, PanelErrorProps } from "./PanelErrorComponent";
|
|
||||||
import DeleteFeedback from "../../Common/DeleteFeedback";
|
import DeleteFeedback from "../../Common/DeleteFeedback";
|
||||||
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
|
import { Collection } from "../../Contracts/ViewModels";
|
||||||
|
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
||||||
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import LoadingIndicator_3Squares from "../../../images/LoadingIndicator_3Squares.gif";
|
import { PanelFooterComponent } from "./PanelFooterComponent";
|
||||||
|
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
|
||||||
|
import { PanelLoadingScreen } from "./PanelLoadingScreen";
|
||||||
export interface DeleteCollectionConfirmationPanelProps {
|
export interface DeleteCollectionConfirmationPanelProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
closePanel: () => void;
|
closePanel: () => void;
|
||||||
@ -44,8 +43,8 @@ export class DeleteCollectionConfirmationPanel extends React.Component<
|
|||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className="panelContentContainer">
|
<form className="panelFormWrapper" onSubmit={this.submit.bind(this)}>
|
||||||
<PanelErrorComponent {...this.getPanelErrorProps()} />
|
<PanelInfoErrorComponent {...this.getPanelErrorProps()} />
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
<div className="confirmDeleteInput">
|
<div className="confirmDeleteInput">
|
||||||
<span className="mandatoryStar">* </span>
|
<span className="mandatoryStar">* </span>
|
||||||
@ -79,18 +78,16 @@ export class DeleteCollectionConfirmationPanel extends React.Component<
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<PanelFooterComponent buttonLabel="OK" onOKButtonClicked={() => this.submit()} />
|
<PanelFooterComponent buttonLabel="OK" />
|
||||||
<div className="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" hidden={!this.state.isExecuting}>
|
{this.state.isExecuting && <PanelLoadingScreen />}
|
||||||
<img className="dataExplorerLoader" src={LoadingIndicator_3Squares} />
|
</form>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getPanelErrorProps(): PanelErrorProps {
|
private getPanelErrorProps(): PanelInfoErrorProps {
|
||||||
if (this.state.formError) {
|
if (this.state.formError) {
|
||||||
return {
|
return {
|
||||||
isWarning: false,
|
messageType: "error",
|
||||||
message: this.state.formError,
|
message: this.state.formError,
|
||||||
showErrorDetails: true,
|
showErrorDetails: true,
|
||||||
openNotificationConsole: this.props.openNotificationConsole,
|
openNotificationConsole: this.props.openNotificationConsole,
|
||||||
@ -98,7 +95,7 @@ export class DeleteCollectionConfirmationPanel extends React.Component<
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isWarning: true,
|
messageType: "warning",
|
||||||
showErrorDetails: false,
|
showErrorDetails: false,
|
||||||
message:
|
message:
|
||||||
"Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.",
|
"Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.",
|
||||||
@ -109,9 +106,10 @@ export class DeleteCollectionConfirmationPanel extends React.Component<
|
|||||||
return this.props.explorer.isLastCollection() && !this.props.explorer.isSelectedDatabaseShared();
|
return this.props.explorer.isLastCollection() && !this.props.explorer.isSelectedDatabaseShared();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async submit(): Promise<void> {
|
public async submit(event: React.FormEvent<HTMLFormElement>): Promise<void> {
|
||||||
const collection = this.props.explorer.findSelectedCollection();
|
event.preventDefault();
|
||||||
|
|
||||||
|
const collection = this.props.explorer.findSelectedCollection();
|
||||||
if (!collection || this.inputCollectionName !== collection.id()) {
|
if (!collection || this.inputCollectionName !== collection.id()) {
|
||||||
const errorMessage = "Input collection name does not match the selected collection";
|
const errorMessage = "Input collection name does not match the selected collection";
|
||||||
this.setState({ formError: errorMessage });
|
this.setState({ formError: errorMessage });
|
||||||
|
@ -1,12 +1,58 @@
|
|||||||
@import "../../../less/Common/Constants";
|
@import "../../../less/Common/Constants";
|
||||||
|
|
||||||
.panelContentContainer {
|
.panelFormWrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
.panelMainContent {
|
.panelMainContent {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
padding: 0 34px;
|
||||||
|
margin: 20px 0;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
margin-bottom: @DefaultSpace;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
margin-bottom: @SmallSpace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.panelInfoIcon {
|
||||||
|
font-size: @mediumFontSize;
|
||||||
|
width: @mediumFontSize;
|
||||||
|
margin: auto 0 auto @SmallSpace;
|
||||||
|
color: @InfoIconColor;
|
||||||
|
cursor: default;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panelTextBold {
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panelTextField {
|
||||||
|
font-size: @mediumFontSize;
|
||||||
|
border: 1px solid #605e5c;
|
||||||
|
color: #000;
|
||||||
|
padding: 4px 10px;
|
||||||
|
width: @newCollectionPaneInputWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panelRadioBtn {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panelRadioBtnLabel {
|
||||||
|
font-size: @mediumFontSize;
|
||||||
|
padding: 0 @LargeSpace 0 @SmallSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsibleSection {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,26 +62,30 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panelWarningErrorContainer {
|
.panelInfoErrorContainer {
|
||||||
background-color: @BaseLow;
|
background-color: @BaseLow;
|
||||||
padding: @DefaultSpace;
|
padding: @DefaultSpace;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
margin-bottom: 24px;
|
margin: 20px 34px 0 34px;
|
||||||
|
|
||||||
.panelWarningIcon {
|
i {
|
||||||
font-size: @WarningErrorIconSize;
|
font-size: @WarningErrorIconSize;
|
||||||
width: @WarningErrorIconSize;
|
width: @WarningErrorIconSize;
|
||||||
margin: auto 0 auto @SmallSpace;
|
margin-left: @SmallSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panelWarningIcon {
|
||||||
color: @WarningIconColor;
|
color: @WarningIconColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panelErrorIcon {
|
.panelErrorIcon {
|
||||||
font-size: @WarningErrorIconSize;
|
|
||||||
width: @WarningErrorIconSize;
|
|
||||||
margin: auto 0 auto @SmallSpace;
|
|
||||||
color: @ErrorIconColor;
|
color: @ErrorIconColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.panelLargeInfoIcon {
|
||||||
|
color: @InfoIconColor;
|
||||||
|
}
|
||||||
|
|
||||||
.panelWarningErrorDetailsLinkContainer {
|
.panelWarningErrorDetailsLinkContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -48,10 +98,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.panelFooter button {
|
.panelFooter {
|
||||||
|
padding: 20px 34px;
|
||||||
|
border-top: solid 1px #bbbbbb;
|
||||||
|
|
||||||
|
& button {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.deleteCollectionFeedback {
|
.deleteCollectionFeedback {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.panelGroupSpacing > * {
|
||||||
|
margin-bottom: @SmallSpace;
|
||||||
|
}
|
||||||
|
@ -9,10 +9,30 @@ export interface PanelContainerProps {
|
|||||||
closePanel: () => void;
|
closePanel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PanelContainerComponent extends React.Component<PanelContainerProps> {
|
export interface PanelContainerState {
|
||||||
|
height: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PanelContainerComponent extends React.Component<PanelContainerProps, PanelContainerState> {
|
||||||
private static readonly consoleHeaderHeight = 32;
|
private static readonly consoleHeaderHeight = 32;
|
||||||
private static readonly consoleContentHeight = 220;
|
private static readonly consoleContentHeight = 220;
|
||||||
|
|
||||||
|
constructor(props: PanelContainerProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
height: this.getPanelHeight(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
window.addEventListener("resize", () => this.setState({ height: this.getPanelHeight() }));
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
window.removeEventListener("resize", () => this.setState({ height: this.getPanelHeight() }));
|
||||||
|
}
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
if (!this.props.panelContent) {
|
if (!this.props.panelContent) {
|
||||||
return <></>;
|
return <></>;
|
||||||
@ -30,8 +50,10 @@ export class PanelContainerComponent extends React.Component<PanelContainerProps
|
|||||||
headerClassName="panelHeader"
|
headerClassName="panelHeader"
|
||||||
styles={{
|
styles={{
|
||||||
navigation: { borderBottom: "1px solid #cccccc" },
|
navigation: { borderBottom: "1px solid #cccccc" },
|
||||||
content: { padding: "24px 34px 20px 34px", height: "100%" },
|
content: { padding: 0, height: "100%" },
|
||||||
scrollableContent: { height: "100%" },
|
scrollableContent: { height: "100%" },
|
||||||
|
header: { padding: "0 0 8px 34px" },
|
||||||
|
commands: { marginTop: 8 },
|
||||||
}}
|
}}
|
||||||
style={{ height: this.getPanelHeight() }}
|
style={{ height: this.getPanelHeight() }}
|
||||||
>
|
>
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { Icon, Text } from "office-ui-fabric-react";
|
|
||||||
|
|
||||||
export interface PanelErrorProps {
|
|
||||||
message: string;
|
|
||||||
isWarning: boolean;
|
|
||||||
showErrorDetails: boolean;
|
|
||||||
openNotificationConsole?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PanelErrorComponent: React.FunctionComponent<PanelErrorProps> = (props: PanelErrorProps): JSX.Element => (
|
|
||||||
<div className="panelWarningErrorContainer">
|
|
||||||
{props.isWarning ? (
|
|
||||||
<Icon iconName="WarningSolid" className="panelWarningIcon" />
|
|
||||||
) : (
|
|
||||||
<Icon iconName="StatusErrorFull" className="panelErrorIcon" />
|
|
||||||
)}
|
|
||||||
<span className="panelWarningErrorDetailsLinkContainer">
|
|
||||||
<Text className="panelWarningErrorMessage" variant="small">
|
|
||||||
{props.message}
|
|
||||||
</Text>
|
|
||||||
{props.showErrorDetails && (
|
|
||||||
<a className="paneErrorLink" role="link" onClick={props.openNotificationConsole}>
|
|
||||||
More details
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
@ -3,13 +3,12 @@ import { PrimaryButton } from "office-ui-fabric-react";
|
|||||||
|
|
||||||
export interface PanelFooterProps {
|
export interface PanelFooterProps {
|
||||||
buttonLabel: string;
|
buttonLabel: string;
|
||||||
onOKButtonClicked: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PanelFooterComponent: React.FunctionComponent<PanelFooterProps> = (
|
export const PanelFooterComponent: React.FunctionComponent<PanelFooterProps> = (
|
||||||
props: PanelFooterProps
|
props: PanelFooterProps
|
||||||
): JSX.Element => (
|
): JSX.Element => (
|
||||||
<div className="panelFooter">
|
<div className="panelFooter">
|
||||||
<PrimaryButton id="sidePanelOkButton" text={props.buttonLabel} onClick={() => props.onOKButtonClicked()} />
|
<PrimaryButton type="submit" id="sidePanelOkButton" text={props.buttonLabel} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
45
src/Explorer/Panes/PanelInfoErrorComponent.tsx
Normal file
45
src/Explorer/Panes/PanelInfoErrorComponent.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Icon, Link, Stack, Text } from "office-ui-fabric-react";
|
||||||
|
|
||||||
|
export interface PanelInfoErrorProps {
|
||||||
|
message: string;
|
||||||
|
messageType: string;
|
||||||
|
showErrorDetails: boolean;
|
||||||
|
link?: string;
|
||||||
|
linkText?: string;
|
||||||
|
openNotificationConsole?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProps> = (
|
||||||
|
props: PanelInfoErrorProps
|
||||||
|
): JSX.Element => {
|
||||||
|
let icon: JSX.Element;
|
||||||
|
if (props.messageType === "error") {
|
||||||
|
icon = <Icon iconName="StatusErrorFull" className="panelErrorIcon" />;
|
||||||
|
} else if (props.messageType === "warning") {
|
||||||
|
icon = <Icon iconName="WarningSolid" className="panelWarningIcon" />;
|
||||||
|
} else if (props.messageType === "info") {
|
||||||
|
icon = <Icon iconName="InfoSolid" className="panelLargeInfoIcon" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack className="panelInfoErrorContainer" horizontal verticalAlign="start">
|
||||||
|
{icon}
|
||||||
|
<span className="panelWarningErrorDetailsLinkContainer">
|
||||||
|
<Text className="panelWarningErrorMessage" variant="small">
|
||||||
|
{props.message}{" "}
|
||||||
|
{props.link && props.linkText && (
|
||||||
|
<Link target="_blank" href={props.link}>
|
||||||
|
{props.linkText}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
{props.showErrorDetails && (
|
||||||
|
<a className="paneErrorLink" role="link" onClick={props.openNotificationConsole}>
|
||||||
|
More details
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
8
src/Explorer/Panes/PanelLoadingScreen.tsx
Normal file
8
src/Explorer/Panes/PanelLoadingScreen.tsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import React from "react";
|
||||||
|
import LoadingIndicator_3Squares from "../../../images/LoadingIndicator_3Squares.gif";
|
||||||
|
|
||||||
|
export const PanelLoadingScreen: React.FunctionComponent = () => (
|
||||||
|
<div className="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer">
|
||||||
|
<img className="dataExplorerLoader" src={LoadingIndicator_3Squares} />
|
||||||
|
</div>
|
||||||
|
);
|
@ -15,20 +15,27 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
}
|
}
|
||||||
openNotificationConsole={[Function]}
|
openNotificationConsole={[Function]}
|
||||||
>
|
>
|
||||||
<div
|
<form
|
||||||
className="panelContentContainer"
|
className="panelFormWrapper"
|
||||||
|
onSubmit={[Function]}
|
||||||
>
|
>
|
||||||
<PanelErrorComponent
|
<PanelInfoErrorComponent
|
||||||
isWarning={true}
|
|
||||||
message="Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources."
|
message="Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources."
|
||||||
|
messageType="warning"
|
||||||
showErrorDetails={false}
|
showErrorDetails={false}
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
className="panelInfoErrorContainer"
|
||||||
|
horizontal={true}
|
||||||
|
verticalAlign="start"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="panelWarningErrorContainer"
|
className="ms-Stack panelInfoErrorContainer css-140"
|
||||||
>
|
>
|
||||||
<StyledIconBase
|
<StyledIconBase
|
||||||
className="panelWarningIcon"
|
className="panelWarningIcon"
|
||||||
iconName="WarningSolid"
|
iconName="WarningSolid"
|
||||||
|
key=".0:$.0"
|
||||||
>
|
>
|
||||||
<IconBase
|
<IconBase
|
||||||
className="panelWarningIcon"
|
className="panelWarningIcon"
|
||||||
@ -310,7 +317,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
aria-hidden={true}
|
aria-hidden={true}
|
||||||
className="panelWarningIcon root-141"
|
className="panelWarningIcon root-142"
|
||||||
data-icon-name="WarningSolid"
|
data-icon-name="WarningSolid"
|
||||||
>
|
>
|
||||||
|
|
||||||
@ -319,20 +326,23 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
</StyledIconBase>
|
</StyledIconBase>
|
||||||
<span
|
<span
|
||||||
className="panelWarningErrorDetailsLinkContainer"
|
className="panelWarningErrorDetailsLinkContainer"
|
||||||
|
key=".0:$.1"
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
className="panelWarningErrorMessage"
|
className="panelWarningErrorMessage"
|
||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="panelWarningErrorMessage css-142"
|
className="panelWarningErrorMessage css-143"
|
||||||
>
|
>
|
||||||
Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.
|
Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
</Text>
|
</Text>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</PanelErrorComponent>
|
</Stack>
|
||||||
|
</PanelInfoErrorComponent>
|
||||||
<div
|
<div
|
||||||
className="panelMainContent"
|
className="panelMainContent"
|
||||||
>
|
>
|
||||||
@ -348,7 +358,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="css-142"
|
className="css-143"
|
||||||
>
|
>
|
||||||
Confirm by typing the collection id
|
Confirm by typing the collection id
|
||||||
</span>
|
</span>
|
||||||
@ -649,18 +659,18 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
validateOnLoad={true}
|
validateOnLoad={true}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField root-144"
|
className="ms-TextField root-145"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-wrapper"
|
className="ms-TextField-wrapper"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-fieldGroup fieldGroup-145"
|
className="ms-TextField-fieldGroup fieldGroup-146"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className="ms-TextField-field field-146"
|
className="ms-TextField-field field-147"
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@ -683,7 +693,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="css-155"
|
className="css-156"
|
||||||
>
|
>
|
||||||
Help us improve Azure Cosmos DB!
|
Help us improve Azure Cosmos DB!
|
||||||
</span>
|
</span>
|
||||||
@ -693,7 +703,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="css-155"
|
className="css-156"
|
||||||
>
|
>
|
||||||
What is the reason why you are deleting this container?
|
What is the reason why you are deleting this container?
|
||||||
</span>
|
</span>
|
||||||
@ -996,17 +1006,17 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
validateOnLoad={true}
|
validateOnLoad={true}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField ms-TextField--multiline root-144"
|
className="ms-TextField ms-TextField--multiline root-145"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-wrapper"
|
className="ms-TextField-wrapper"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-fieldGroup fieldGroup-156"
|
className="ms-TextField-fieldGroup fieldGroup-157"
|
||||||
>
|
>
|
||||||
<textarea
|
<textarea
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
className="ms-TextField-field field-157"
|
className="ms-TextField-field field-158"
|
||||||
id="deleteCollectionFeedbackInput"
|
id="deleteCollectionFeedbackInput"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@ -1024,19 +1034,17 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
</div>
|
</div>
|
||||||
<PanelFooterComponent
|
<PanelFooterComponent
|
||||||
buttonLabel="OK"
|
buttonLabel="OK"
|
||||||
onOKButtonClicked={[Function]}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="panelFooter"
|
className="panelFooter"
|
||||||
>
|
>
|
||||||
<CustomizedPrimaryButton
|
<CustomizedPrimaryButton
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onClick={[Function]}
|
|
||||||
text="OK"
|
text="OK"
|
||||||
|
type="submit"
|
||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onClick={[Function]}
|
|
||||||
text="OK"
|
text="OK"
|
||||||
theme={
|
theme={
|
||||||
Object {
|
Object {
|
||||||
@ -1311,10 +1319,10 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
type="submit"
|
||||||
>
|
>
|
||||||
<CustomizedDefaultButton
|
<CustomizedDefaultButton
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onClick={[Function]}
|
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
text="OK"
|
text="OK"
|
||||||
@ -1591,10 +1599,10 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
type="submit"
|
||||||
>
|
>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onClick={[Function]}
|
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
text="OK"
|
text="OK"
|
||||||
@ -1871,11 +1879,11 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
type="submit"
|
||||||
>
|
>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
baseClassName="ms-Button"
|
baseClassName="ms-Button"
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onClick={[Function]}
|
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
split={false}
|
split={false}
|
||||||
@ -2696,10 +2704,11 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
type="submit"
|
||||||
variantClassName="ms-Button--primary"
|
variantClassName="ms-Button--primary"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="ms-Button ms-Button--primary root-159"
|
className="ms-Button ms-Button--primary root-160"
|
||||||
data-is-focusable={true}
|
data-is-focusable={true}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
@ -2708,17 +2717,17 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
onKeyUp={[Function]}
|
onKeyUp={[Function]}
|
||||||
onMouseDown={[Function]}
|
onMouseDown={[Function]}
|
||||||
onMouseUp={[Function]}
|
onMouseUp={[Function]}
|
||||||
type="button"
|
type="submit"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-flexContainer flexContainer-160"
|
className="ms-Button-flexContainer flexContainer-161"
|
||||||
data-automationid="splitbuttonprimary"
|
data-automationid="splitbuttonprimary"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-textContainer textContainer-161"
|
className="ms-Button-textContainer textContainer-162"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-label label-163"
|
className="ms-Button-label label-164"
|
||||||
id="id__6"
|
id="id__6"
|
||||||
key="id__6"
|
key="id__6"
|
||||||
>
|
>
|
||||||
@ -2735,15 +2744,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
</CustomizedPrimaryButton>
|
</CustomizedPrimaryButton>
|
||||||
</div>
|
</div>
|
||||||
</PanelFooterComponent>
|
</PanelFooterComponent>
|
||||||
<div
|
</form>
|
||||||
className="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer"
|
|
||||||
hidden={true}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
className="dataExplorerLoader"
|
|
||||||
src=""
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DeleteCollectionConfirmationPanel>
|
</DeleteCollectionConfirmationPanel>
|
||||||
`;
|
`;
|
||||||
|
@ -16,9 +16,15 @@ exports[`PaneContainerComponent test should be resize if notification console is
|
|||||||
}
|
}
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
|
"commands": Object {
|
||||||
|
"marginTop": 8,
|
||||||
|
},
|
||||||
"content": Object {
|
"content": Object {
|
||||||
"height": "100%",
|
"height": "100%",
|
||||||
"padding": "24px 34px 20px 34px",
|
"padding": 0,
|
||||||
|
},
|
||||||
|
"header": Object {
|
||||||
|
"padding": "0 0 8px 34px",
|
||||||
},
|
},
|
||||||
"navigation": Object {
|
"navigation": Object {
|
||||||
"borderBottom": "1px solid #cccccc",
|
"borderBottom": "1px solid #cccccc",
|
||||||
@ -52,9 +58,15 @@ exports[`PaneContainerComponent test should render with panel content and header
|
|||||||
}
|
}
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
|
"commands": Object {
|
||||||
|
"marginTop": 8,
|
||||||
|
},
|
||||||
"content": Object {
|
"content": Object {
|
||||||
"height": "100%",
|
"height": "100%",
|
||||||
"padding": "24px 34px 20px 34px",
|
"padding": 0,
|
||||||
|
},
|
||||||
|
"header": Object {
|
||||||
|
"padding": "0 0 8px 34px",
|
||||||
},
|
},
|
||||||
"navigation": Object {
|
"navigation": Object {
|
||||||
"borderBottom": "1px solid #cccccc",
|
"borderBottom": "1px solid #cccccc",
|
||||||
|
@ -45,6 +45,7 @@ import "./Explorer/Controls/DynamicList/DynamicListComponent.less";
|
|||||||
import "./Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.less";
|
import "./Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.less";
|
||||||
import "./Explorer/Controls/JsonEditor/JsonEditorComponent.less";
|
import "./Explorer/Controls/JsonEditor/JsonEditorComponent.less";
|
||||||
import "./Explorer/Controls/Notebook/NotebookTerminalComponent.less";
|
import "./Explorer/Controls/Notebook/NotebookTerminalComponent.less";
|
||||||
|
import "./Explorer/Controls/ThroughputInput/ThroughputInput.less";
|
||||||
import "./Explorer/Controls/TreeComponent/treeComponent.less";
|
import "./Explorer/Controls/TreeComponent/treeComponent.less";
|
||||||
import { ExplorerParams } from "./Explorer/Explorer";
|
import { ExplorerParams } from "./Explorer/Explorer";
|
||||||
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
|
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
|
||||||
|
Loading…
Reference in New Issue
Block a user