resolve merge conflict

This commit is contained in:
hardiknai-techm
2021-05-04 13:03:01 +05:30
75 changed files with 6769 additions and 3886 deletions

View File

@@ -26,4 +26,3 @@ ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPane
ko.components.register("table-add-entity-pane", new PaneComponents.TableAddEntityPaneComponent());
ko.components.register("table-edit-entity-pane", new PaneComponents.TableEditEntityPaneComponent());
ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent());
ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent());

View File

@@ -29,11 +29,11 @@ export interface DatabaseContextMenuButtonParams {
* New resource tree (in ReactJS)
*/
export class ResourceTreeContextMenuButtonFactory {
public static createDatabaseContextMenu(container: Explorer): TreeNodeMenuItem[] {
public static createDatabaseContextMenu(container: Explorer, databaseId: string): TreeNodeMenuItem[] {
const items: TreeNodeMenuItem[] = [
{
iconSrc: AddCollectionIcon,
onClick: () => container.onNewCollectionClicked(),
onClick: () => container.onNewCollectionClicked(databaseId),
label: container.addCollectionText(),
},
];

View File

@@ -5,6 +5,7 @@ import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
export interface CollapsibleSectionProps {
title: string;
isExpandedByDefault: boolean;
onExpand?: () => void;
}
export interface CollapsibleSectionState {
@@ -23,6 +24,12 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
this.setState({ isExpanded: !this.state.isExpanded });
};
public componentDidUpdate(): void {
if (this.state.isExpanded && this.props.onExpand) {
this.props.onExpand();
}
}
public render(): JSX.Element {
return (
<>

View File

@@ -23,8 +23,6 @@ export interface RepoListItem {
}
export class GitHubReposComponent extends React.Component<GitHubReposComponentProps> {
public static readonly ConnectToGitHubTitle = "Connect to GitHub";
public static readonly ManageGitHubRepoTitle = "Manage GitHub settings";
private static readonly ManageGitHubRepoDescription =
"Select your GitHub repos and branch(es) to pin to your notebooks workspace.";
private static readonly ManageGitHubRepoResetConnection = "View or change your GitHub authorization settings.";
@@ -32,14 +30,6 @@ export class GitHubReposComponent extends React.Component<GitHubReposComponentPr
private static readonly CancelButtonText = "Cancel";
public render(): JSX.Element {
const header: JSX.Element = (
<p>
{this.props.showAuthorizeAccess
? GitHubReposComponent.ConnectToGitHubTitle
: GitHubReposComponent.ManageGitHubRepoTitle}
</p>
);
const content: JSX.Element = this.props.showAuthorizeAccess ? (
<AuthorizeAccessComponent {...this.props.authorizeAccessProps} />
) : (
@@ -66,9 +56,6 @@ export class GitHubReposComponent extends React.Component<GitHubReposComponentPr
return (
<>
<div className={"firstdivbg headerline"} role="heading" aria-level={2}>
{header}
</div>
<div className={"paneMainContent"}>{content}</div>
{!this.props.showAuthorizeAccess && (
<>

View File

@@ -1,19 +1,21 @@
import {
IStyleFunctionOrObject,
ICheckboxStyleProps,
ICheckboxStyles,
IDropdownStyles,
IDropdownStyleProps,
IDropdownStyles,
IStyleFunctionOrObject,
} from "office-ui-fabric-react";
export const ButtonsFooterStyle: React.CSSProperties = {
padding: 14,
paddingTop: 14,
height: "auto",
borderTop: "2px solid lightGray",
};
export const ContentFooterStyle: React.CSSProperties = {
padding: "10px 24px 10px 24px",
paddingTop: "10px",
height: "auto",
borderTop: "2px solid lightGray",
};
export const ChildrenMargin = 10;
@@ -53,6 +55,11 @@ export const BranchesDropdownOptionContainerStyle: React.CSSProperties = {
padding: 8,
};
export const ContentMainStyle: React.CSSProperties = {
display: "flex",
flexDirection: "column",
};
export const ReposListRepoColumnMinWidth = 192;
export const ReposListBranchesColumnWidth = 116;
export const BranchesDropdownWidth = 200;

View File

@@ -415,7 +415,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
</Text>
<Text>
<em>
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
</em>
</Text>
</Stack>
@@ -689,7 +689,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
</Text>
<Text>
<em>
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
</em>
</Text>
</Stack>

View File

@@ -150,7 +150,7 @@ exports[`SettingsUtils functions render 1`] = `
</Text>
<Text>
<em>
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
</em>
</Text>
</Stack>

View File

@@ -1,6 +1,6 @@
import { shallow } from "enzyme";
import React from "react";
import { CostEstimateText } from ".";
import { CostEstimateText } from "./CostEstimateText";
const props = {
requestUnits: 5,
isAutoscale: false,

View File

@@ -1,8 +1,17 @@
import { Text } from "office-ui-fabric-react";
import React, { FunctionComponent } from "react";
import { Tooltip } from "../../../../Common/Tooltip/Tooltip";
import * as SharedConstants from "../../../../Shared/Constants";
import { userContext } from "../../../../UserContext";
import * as PricingUtils from "../../../../Utils/PricingUtils";
import {
calculateEstimateNumber,
computeRUUsagePriceHourly,
getAutoscalePricePerRu,
getCurrencySign,
getMultimasterMultiplier,
getPriceCurrency,
getPricePerRu,
} from "../../../../Utils/PricingUtils";
interface CostEstimateTextProps {
requestUnits: number;
@@ -13,15 +22,15 @@ export const CostEstimateText: FunctionComponent<CostEstimateTextProps> = ({
requestUnits,
isAutoscale,
}: CostEstimateTextProps) => {
const databaseAccount = userContext.databaseAccount;
if (!databaseAccount || !databaseAccount.properties) {
const { databaseAccount } = userContext;
if (!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({
const hourlyPrice: number = computeRUUsagePriceHourly({
serverId,
requestUnits,
numberOfRegions,
@@ -30,20 +39,22 @@ export const CostEstimateText: FunctionComponent<CostEstimateTextProps> = ({
});
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 currency: string = getPriceCurrency(serverId);
const currencySign: string = getCurrencySign(serverId);
const multiplier = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
const pricePerRu = isAutoscale
? PricingUtils.getAutoscalePricePerRu(serverId, multiplier) * multiplier
: PricingUtils.getPricePerRu(serverId) * multiplier;
? getAutoscalePricePerRu(serverId, multiplier) * multiplier
: getPricePerRu(serverId) * multiplier;
const iconWithEstimatedCostDisclaimer: JSX.Element = <Tooltip>PricingUtils.estimatedCostDisclaimer</Tooltip>;
if (isAutoscale) {
return (
<Text variant="small">
Estimated monthly cost ({currency}):{" "}
Estimated monthly cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
<b>
{currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice / 10)} -{" "}
{currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice)}{" "}
{currencySign + calculateEstimateNumber(monthlyPrice / 10)} -{" "}
{currencySign + calculateEstimateNumber(monthlyPrice)}{" "}
</b>
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits / 10} - {requestUnits}{" "}
RU/s, {currencySign + pricePerRu}/RU)
@@ -53,16 +64,14 @@ export const CostEstimateText: FunctionComponent<CostEstimateTextProps> = ({
return (
<Text variant="small">
Cost ({currency}):{" "}
Estimated cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
<b>
{currencySign + PricingUtils.calculateEstimateNumber(hourlyPrice)} hourly /{" "}
{currencySign + PricingUtils.calculateEstimateNumber(dailyPrice)} daily /{" "}
{currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice)} monthly{" "}
{currencySign + calculateEstimateNumber(hourlyPrice)} hourly /{" "}
{currencySign + calculateEstimateNumber(dailyPrice)} daily /{" "}
{currencySign + calculateEstimateNumber(monthlyPrice)} monthly{" "}
</b>
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits}RU/s,{" "}
{currencySign + pricePerRu}/RU)
<br />
<em>{PricingUtils.estimatedCostDisclaimer}</em>
</Text>
);
};

View File

@@ -11,6 +11,6 @@
padding: 0 @LargeSpace 0 @SmallSpace;
}
.throughputInputSpacing {
margin-bottom: @SmallSpace;
.throughputInputSpacing > :not(:last-child) {
margin-bottom: @DefaultSpace;
}

View File

@@ -4,6 +4,7 @@ import { ThroughputInput } from "./ThroughputInput";
const props = {
isDatabase: false,
showFreeTierExceedThroughputTooltip: true,
isSharded: false,
setThroughputValue: () => jest.fn(),
setIsAutoscale: () => jest.fn(),
onCostAcknowledgeChange: () => jest.fn(),
@@ -22,7 +23,7 @@ describe("ThroughputInput Pane", () => {
it("test Autoscale Mode select", () => {
wrapper.setProps({ isAutoscaleSelected: true });
expect(wrapper.find('[data-testid="ruDescription"]').at(0).text()).toContain(
"Provision maximum RU/s required by this resource. Estimate your required RU/s with"
"Estimate your required RU/s with capacity calculator."
);
expect(wrapper.find('[data-testid="maxRUDescription"]').at(0).text()).toContain("Max RU/s");
});

View File

@@ -14,13 +14,15 @@ import * as Constants from "../../../Common/Constants";
import { Tooltip } from "../../../Common/Tooltip/Tooltip";
import * as SharedConstants from "../../../Shared/Constants";
import { userContext } from "../../../UserContext";
import { getCollectionName } from "../../../Utils/APITypeUtils";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import * as PricingUtils from "../../../Utils/PricingUtils";
import { CostEstimateText } from "./CostEstimateText";
import "./styles.less";
import { CostEstimateText } from "./CostEstimateText/CostEstimateText";
import "./ThroughputInput.less";
export interface ThroughputInputProps {
isDatabase: boolean;
isSharded: boolean;
showFreeTierExceedThroughputTooltip: boolean;
setThroughputValue: (throughput: number) => void;
setIsAutoscale: (isAutoscale: boolean) => void;
@@ -34,32 +36,40 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
showFreeTierExceedThroughputTooltip,
setThroughputValue,
setIsAutoscale,
isSharded,
isAutoscaleSelected = true,
throughput = AutoPilotUtils.minAutoPilotThroughput,
onCostAcknowledgeChange,
}: ThroughputInputProps) => {
const [isCostAcknowledged, setIsCostAcknowledged] = useState<boolean>(false);
const [throughputError, setThroughputError] = useState<string>("");
const getThroughputLabelText = (): string => {
let throughputHeaderText: string;
if (isAutoscaleSelected) {
return AutoPilotUtils.getAutoPilotHeaderText();
throughputHeaderText = AutoPilotUtils.getAutoPilotHeaderText().toLocaleLowerCase();
} else {
const minRU: string = SharedConstants.CollectionCreation.DefaultCollectionRUs400.toLocaleString();
const maxRU: string = userContext.isTryCosmosDBSubscription
? Constants.TryCosmosExperience.maxRU.toLocaleString()
: "unlimited";
throughputHeaderText = `throughput (${minRU} - ${maxRU} RU/s)`;
}
const minRU: string = SharedConstants.CollectionCreation.DefaultCollectionRUs400.toLocaleString();
const maxRU: string = userContext.isTryCosmosDBSubscription
? Constants.TryCosmosExperience.maxRU.toLocaleString()
: "unlimited";
return isAutoscaleSelected ? AutoPilotUtils.getAutoPilotHeaderText() : `Throughput (${minRU} - ${maxRU} RU/s)`;
return `${isDatabase ? "Database" : getCollectionName()} ${throughputHeaderText}`;
};
const onThroughputValueChange = (newInput: string): void => {
const newThroughput = parseInt(newInput);
setThroughputValue(newThroughput);
setIsAutoscale(isAutoscaleSelected);
if (!isSharded && newThroughput > 10000) {
setThroughputError("Unsharded collections support up to 10,000 RUs");
} else {
setThroughputError("");
}
};
const getAutoScaleTooltip = (): string => {
return `After the first ${AutoPilotUtils.getStorageBasedOnUserInput(throughput)} GB of data stored, the max
RU/s will be automatically upgraded based on the new storage value.`;
const collectionName = getCollectionName().toLocaleLowerCase();
return `Set the max RU/s to the highest RU/s you want your ${collectionName} to scale to. The ${collectionName} will scale between 10% of max RU/s to the max RU/s based on usage.`;
};
const getCostAcknowledgeText = (): string => {
@@ -98,13 +108,13 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
<div className="throughputInputContainer throughputInputSpacing">
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text variant="small" style={{ lineHeight: "20px" }}>
<Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }}>
{getThroughputLabelText()}
</Text>
<Tooltip>{PricingUtils.getRuToolTipText()}</Tooltip>
</Stack>
<Stack>
<Stack horizontal verticalAlign="center">
<ChoiceGroup
selectedKey={"" + isAutoscaleSelected}
options={optionList}
@@ -116,7 +126,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
{isAutoscaleSelected && (
<Stack className="throughputInputSpacing">
<Text variant="small" data-testid="ruDescription">
Provision maximum RU/s required by this resource. Estimate your required RU/s with&nbsp;
Estimate your required RU/s with&nbsp;
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/" data-testid="ruDescription">
capacity calculator
</Link>
@@ -124,8 +134,8 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
</Text>
<Stack horizontal>
<Text variant="small" style={{ lineHeight: "20px" }} data-testid="maxRUDescription">
Max RU/s
<Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }} data-testid="maxRUDescription">
{isDatabase ? "Database" : getCollectionName()} Max RU/s
</Text>
<Tooltip>{getAutoScaleTooltip()}</Tooltip>
</Stack>
@@ -142,10 +152,12 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
value={throughput.toString()}
aria-label="Max request units per second"
required={true}
errorMessage={throughputError}
/>
<Text variant="small">
Your {isDatabase ? "database" : "container"} throughput will automatically scale from{" "}
Your {isDatabase ? "database" : getCollectionName().toLocaleLowerCase()} throughput will automatically scale
from{" "}
<b>
{AutoPilotUtils.getMinRUsBasedOnUserInput(throughput)} RU/s (10% of max RU/s) - {throughput} RU/s
</b>{" "}
@@ -186,6 +198,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
value={throughput.toString()}
aria-label="Max request units per second"
required={true}
errorMessage={throughputError}
/>
</TooltipHost>
</Stack>

View File

@@ -3,6 +3,7 @@
exports[`ThroughputInput Pane should render Default properly 1`] = `
<ThroughputInput
isDatabase={false}
isSharded={false}
onCostAcknowledgeChange={[Function]}
setIsAutoscale={[Function]}
setThroughputValue={[Function]}
@@ -27,6 +28,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
key=".0:$.1"
style={
Object {
"fontWeight": 600,
"lineHeight": "20px",
}
}
@@ -36,11 +38,12 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
className="css-41"
style={
Object {
"fontWeight": 600,
"lineHeight": "20px",
}
}
>
Throughput (autoscale)
Container throughput (autoscale)
</span>
</Text>
<Tooltip
@@ -1423,7 +1426,10 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
</Tooltip>
</div>
</Stack>
<Stack>
<Stack
horizontal={true}
verticalAlign="center"
>
<div
className="ms-Stack css-51"
>
@@ -2426,7 +2432,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
className="throughputInputSpacing"
>
<div
className="ms-Stack throughputInputSpacing css-51"
className="ms-Stack throughputInputSpacing css-63"
>
<Text
data-testid="ruDescription"
@@ -2437,7 +2443,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
className="css-41"
data-testid="ruDescription"
>
Provision maximum RU/s required by this resource. Estimate your required RU/s with 
Estimate your required RU/s with 
<StyledLinkBase
data-testid="ruDescription"
href="https://cosmos.azure.com/capacitycalculator/"
@@ -2723,7 +2729,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
}
>
<a
className="ms-Link root-63"
className="ms-Link root-64"
data-testid="ruDescription"
href="https://cosmos.azure.com/capacitycalculator/"
onClick={[Function]}
@@ -2748,6 +2754,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
key=".0:$.0"
style={
Object {
"fontWeight": 600,
"lineHeight": "20px",
}
}
@@ -2758,11 +2765,13 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
data-testid="maxRUDescription"
style={
Object {
"fontWeight": 600,
"lineHeight": "20px",
}
}
>
Max RU/s
Container
Max RU/s
</span>
</Text>
<Tooltip
@@ -2775,8 +2784,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"gapSpace": 0,
}
}
content="After the first 40 GB of data stored, the max
RU/s will be automatically upgraded based on the new storage value."
content="Set the max RU/s to the highest RU/s you want your container to scale to. The container will scale between 10% of max RU/s to the max RU/s based on usage."
id="tooltip8"
styles={
Object {
@@ -2792,8 +2800,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"gapSpace": 0,
}
}
content="After the first 40 GB of data stored, the max
RU/s will be automatically upgraded based on the new storage value."
content="Set the max RU/s to the highest RU/s you want your container to scale to. The container will scale between 10% of max RU/s to the max RU/s based on usage."
delay={1}
id="tooltip8"
styles={[Function]}
@@ -4149,6 +4156,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
</Stack>
<StyledTextFieldBase
aria-label="Max request units per second"
errorMessage=""
key=".0:$.2"
min={4000}
onChange={[Function]}
@@ -4172,6 +4180,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-label="Max request units per second"
canRevealPassword={false}
deferredValidationTime={200}
errorMessage=""
min={4000}
onChange={[Function]}
required={true}
@@ -4456,17 +4465,17 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
value="4000"
>
<div
className="ms-TextField is-required root-65"
className="ms-TextField is-required root-66"
>
<div
className="ms-TextField-wrapper"
>
<div
className="ms-TextField-fieldGroup fieldGroup-66"
className="ms-TextField-fieldGroup fieldGroup-67"
>
<input
aria-invalid={false}
className="ms-TextField-field field-67"
className="ms-TextField-field field-68"
id="TextField14"
min={4000}
onBlur={[Function]}

View File

@@ -7,7 +7,7 @@ import _ from "underscore";
import { AuthType } from "../AuthType";
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
import * as Constants from "../Common/Constants";
import { ExplorerMetrics } from "../Common/Constants";
import { ExplorerMetrics, HttpStatusCodes } from "../Common/Constants";
import { readCollection } from "../Common/dataAccess/readCollection";
import { readDatabases } from "../Common/dataAccess/readDatabases";
import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils";
@@ -19,7 +19,9 @@ import { configContext, Platform } from "../ConfigContext";
import * as DataModels from "../Contracts/DataModels";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import * as ViewModels from "../Contracts/ViewModels";
import { IGalleryItem } from "../Juno/JunoClient";
import { GitHubClient } from "../GitHub/GitHubClient";
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
import { NotebookWorkspaceManager } from "../NotebookWorkspaceManager/NotebookWorkspaceManager";
import { ResourceProviderClientFactory } from "../ResourceProvider/ResourceProviderClientFactory";
import { RouteHandler } from "../RouteHandlers/RouteHandler";
@@ -30,12 +32,12 @@ import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants"
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
import { ArcadiaResourceManager } from "../SparkClusterManager/ArcadiaResourceManager";
import { userContext } from "../UserContext";
import { getCollectionName } from "../Utils/APITypeUtils";
import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { stringToBlob } from "../Utils/BlobUtils";
import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
import * as PricingUtils from "../Utils/PricingUtils";
import * as ComponentRegisterer from "./ComponentRegisterer";
import { ArcadiaWorkspaceItem } from "./Controls/Arcadia/ArcadiaMenuPicker";
import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent";
@@ -58,6 +60,7 @@ import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel";
import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane";
import { GitHubReposPanel } from "./Panes/GitHubReposPanel/GitHubReposPanel";
import GraphStylingPane from "./Panes/GraphStylingPane";
import { LoadQueryPane } from "./Panes/LoadQueryPane/LoadQueryPane";
import { SaveQueryPane } from "./Panes/SaveQueryPane/SaveQueryPane";
@@ -167,10 +170,11 @@ export default class Explorer {
public addCollectionPane: AddCollectionPane;
public graphStylingPane: GraphStylingPane;
public cassandraAddCollectionPane: CassandraAddCollectionPane;
public gitHubReposPane: ContextualPaneBase;
private gitHubClient: GitHubClient;
public gitHubOAuthService: GitHubOAuthService;
public junoClient: JunoClient;
// features
public isGitHubPaneEnabled: ko.Observable<boolean>;
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
public isHostedDataExplorerEnabled: ko.Computed<boolean>;
public isRightPanelV2Enabled: ko.Computed<boolean>;
@@ -212,6 +216,8 @@ export default class Explorer {
private static readonly MaxNbDatabasesToAutoExpand = 5;
constructor(params?: ExplorerParams) {
this.gitHubClient = new GitHubClient(this.onGitHubClientError);
this.junoClient = new JunoClient();
this.setIsNotificationConsoleExpanded = params?.setIsNotificationConsoleExpanded;
this.setNotificationConsoleData = params?.setNotificationConsoleData;
this.setInProgressConsoleDataIdToBeDeleted = params?.setInProgressConsoleDataIdToBeDeleted;
@@ -318,7 +324,6 @@ export default class Explorer {
this.resourceTokenCollectionId = ko.observable<string>();
this.resourceTokenCollection = ko.observable<ViewModels.CollectionBase>();
this.resourceTokenPartitionKey = ko.observable<string>();
this.isGitHubPaneEnabled = ko.observable<boolean>(false);
this.isMongoIndexingEnabled = ko.observable<boolean>(false);
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
@@ -598,9 +603,6 @@ export default class Explorer {
refreshCommandBarButtons: () => this.refreshCommandBarButtons(),
refreshNotebookList: () => this.refreshNotebookList(),
});
this.gitHubReposPane = this.notebookManager.gitHubReposPane;
this.isGitHubPaneEnabled(true);
}
this.refreshCommandBarButtons();
@@ -648,6 +650,23 @@ export default class Explorer {
}
}
private onGitHubClientError = (error: any): void => {
Logger.logError(getErrorMessage(error), "NotebookManager/onGitHubClientError");
if (error.status === HttpStatusCodes.Unauthorized) {
this.gitHubOAuthService.resetToken();
this.showOkCancelModalDialog(
undefined,
"Cosmos DB cannot access your Github account anymore. Please connect to GitHub again.",
"Connect to GitHub",
() => this.openGitHubReposPanel("Connect to GitHub"),
"Cancel",
undefined
);
}
};
public openEnableSynapseLinkDialog(): void {
const addSynapseLinkDialogProps: DialogProps = {
linkProps: {
@@ -1951,14 +1970,14 @@ export default class Explorer {
}
}
public onNewCollectionClicked(): void {
public onNewCollectionClicked(databaseId?: string): void {
if (userContext.apiType === "Cassandra") {
this.cassandraAddCollectionPane.open();
} else if (userContext.features.enableReactPane) {
this.openAddCollectionPanel();
} else {
} else if (userContext.features.enableKOPanel) {
this.addCollectionPane.open(this.selectedDatabaseId());
document.getElementById("linkAddCollection").focus();
} else {
this.openAddCollectionPanel(databaseId);
}
}
@@ -2062,14 +2081,9 @@ export default class Explorer {
}
public openDeleteCollectionConfirmationPane(): void {
let collectionName = PricingUtils.getCollectionName(userContext.apiType);
this.openSidePanel(
"Delete " + collectionName,
<DeleteCollectionConfirmationPane
explorer={this}
collectionName={collectionName}
closePanel={this.closeSidePanel}
/>
"Delete " + getCollectionName(),
<DeleteCollectionConfirmationPane explorer={this} closePanel={this.closeSidePanel} />
);
}
@@ -2090,28 +2104,32 @@ export default class Explorer {
}
public openSettingPane(): void {
this.openSidePanel("Settings", <SettingsPane explorer={this} closePanel={this.closeSidePanel} />);
this.openSidePanel(
"Settings",
<SettingsPane expandConsole={() => this.expandConsole()} closePanel={this.closeSidePanel} />
);
}
public openExecuteSprocParamsPanel(storedProcedure: StoredProcedure): void {
this.openSidePanel(
"Input parameters",
<ExecuteSprocParamsPane
explorer={this}
storedProcedure={storedProcedure}
expandConsole={() => this.expandConsole()}
closePanel={() => this.closeSidePanel()}
/>
);
}
public async openAddCollectionPanel(): Promise<void> {
public async openAddCollectionPanel(databaseId?: string): Promise<void> {
await this.loadDatabaseOffers();
this.openSidePanel(
"New Collection",
"New " + getCollectionName(),
<AddCollectionPanel
explorer={this}
closePanel={() => this.closeSidePanel()}
openNotificationConsole={() => this.expandConsole()}
databaseId={databaseId}
/>
);
}
@@ -2148,13 +2166,26 @@ export default class Explorer {
this.openSidePanel(
"Upload File",
<UploadFilePane
explorer={this}
expandConsole={() => this.expandConsole()}
closePanel={this.closeSidePanel}
uploadFile={(name: string, content: string) => this.uploadFile(name, content, parent)}
/>
);
}
public openGitHubReposPanel(header: string, junoClient?: JunoClient): void {
this.openSidePanel(
header,
<GitHubReposPanel
explorer={this}
closePanel={this.closeSidePanel}
gitHubClientProp={this.notebookManager.gitHubClient}
junoClientProp={junoClient}
openNotificationConsole={() => this.expandConsole()}
/>
);
}
public openAddTableEntityPanel(queryTablesTab: QueryTablesTab, tableEntityListViewModel: TableListViewModal): void {
this.openSidePanel(
"Add Table Entity",

View File

@@ -1,63 +0,0 @@
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { GraphConfig } from "../../Tabs/GraphTab";
import * as ViewModels from "../../../Contracts/ViewModels";
import { GraphExplorer, GraphAccessor } from "./GraphExplorer";
interface Parameter {
onIsNewVertexDisabledChange: (isEnabled: boolean) => void;
onGraphAccessorCreated: (instance: GraphAccessor) => void;
onIsFilterQueryLoading: (isFilterQueryLoading: boolean) => void;
onIsValidQuery: (isValidQuery: boolean) => void;
onIsPropertyEditing: (isEditing: boolean) => void;
onIsGraphDisplayed: (isDisplayed: boolean) => void;
onResetDefaultGraphConfigValues: () => void;
graphConfigUiData: ViewModels.GraphConfigUiData;
graphConfig?: GraphConfig;
collectionPartitionKeyProperty: string;
graphBackendEndpoint: string;
databaseId: string;
collectionId: string;
masterKey: string;
onLoadStartKey: number;
onLoadStartKeyChange: (newKey: number) => void;
resourceId: string;
}
export class GraphExplorerAdapter implements ReactAdapter {
public params: Parameter;
public parameters = {};
public isNewVertexDisabled: boolean;
public constructor(params: Parameter) {
this.params = params;
}
public renderComponent(): JSX.Element {
return (
<GraphExplorer
onIsNewVertexDisabledChange={this.params.onIsNewVertexDisabledChange}
onGraphAccessorCreated={this.params.onGraphAccessorCreated}
onIsFilterQueryLoadingChange={this.params.onIsFilterQueryLoading}
onIsValidQueryChange={this.params.onIsValidQuery}
onIsPropertyEditing={this.params.onIsPropertyEditing}
onIsGraphDisplayed={this.params.onIsGraphDisplayed}
onResetDefaultGraphConfigValues={this.params.onResetDefaultGraphConfigValues}
collectionPartitionKeyProperty={this.params.collectionPartitionKeyProperty}
graphBackendEndpoint={this.params.graphBackendEndpoint}
databaseId={this.params.databaseId}
collectionId={this.params.collectionId}
masterKey={this.params.masterKey}
onLoadStartKey={this.params.onLoadStartKey}
onLoadStartKeyChange={this.params.onLoadStartKeyChange}
resourceId={this.params.resourceId}
/* TODO Figure out how to make this Knockout-free */
graphConfigUiData={this.params.graphConfigUiData}
graphConfig={this.params.graphConfig}
/>
);
}
}

View File

@@ -19,11 +19,8 @@ import SettingsIcon from "../../../../images/settings_15x15.svg";
import SynapseIcon from "../../../../images/synapse-link.svg";
import { AuthType } from "../../../AuthType";
import * as Constants from "../../../Common/Constants";
import { Areas } from "../../../Common/Constants";
import { configContext, Platform } from "../../../ConfigContext";
import * as ViewModels from "../../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../UserContext";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer";
@@ -537,14 +534,7 @@ function createManageGitHubAccountButton(container: Explorer): CommandButtonComp
return {
iconSrc: GitHubIcon,
iconAlt: label,
onCommandClick: () => {
if (!connectedToGitHub) {
TelemetryProcessor.trace(Action.NotebooksGitHubConnect, ActionModifiers.Mark, {
dataExplorerArea: Areas.Notebook,
});
}
container.gitHubReposPane.open();
},
onCommandClick: () => container.openGitHubReposPanel(label),
commandButtonLabel: label,
hasPopup: false,
disabled: false,

View File

@@ -21,7 +21,7 @@ import { getFullName } from "../../Utils/UserUtils";
import Explorer from "../Explorer";
import { ContextualPaneBase } from "../Panes/ContextualPaneBase";
import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane";
import { GitHubReposPane } from "../Panes/GitHubReposPane";
// import { GitHubReposPane } from "../Panes/GitHubReposPane";
import { PublishNotebookPane } from "../Panes/PublishNotebookPane/PublishNotebookPane";
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
import { NotebookContentProvider } from "./NotebookComponent/NotebookContentProvider";
@@ -50,7 +50,7 @@ export default class NotebookManager {
private gitHubContentProvider: GitHubContentProvider;
public gitHubOAuthService: GitHubOAuthService;
private gitHubClient: GitHubClient;
public gitHubClient: GitHubClient;
public gitHubReposPane: ContextualPaneBase;
@@ -60,13 +60,6 @@ export default class NotebookManager {
this.gitHubOAuthService = new GitHubOAuthService(this.junoClient);
this.gitHubClient = new GitHubClient(this.onGitHubClientError);
this.gitHubReposPane = new GitHubReposPane({
id: "gitHubReposPane",
visible: ko.observable<boolean>(false),
container: this.params.container,
junoClient: this.junoClient,
gitHubClient: this.gitHubClient,
});
this.gitHubContentProvider = new GitHubContentProvider({
gitHubClient: this.gitHubClient,
@@ -92,9 +85,9 @@ export default class NotebookManager {
this.gitHubOAuthService.getTokenObservable().subscribe((token) => {
this.gitHubClient.setToken(token?.access_token);
if (this.gitHubReposPane.visible()) {
this.gitHubReposPane.open();
if (this?.gitHubOAuthService.isLoggedIn()) {
this.params.container.closeSidePanel();
this.params.container.openGitHubReposPanel("Manager GitHub settings", this.junoClient);
}
this.params.refreshCommandBarButtons();
@@ -163,7 +156,7 @@ export default class NotebookManager {
undefined,
"Cosmos DB cannot access your Github account anymore. Please connect to GitHub again.",
"Connect to GitHub",
() => this.gitHubReposPane.open(),
() => this.params.container.openGitHubReposPanel("Connect to GitHub"),
"Cancel",
undefined
);

View File

@@ -3,7 +3,6 @@ import { ActionContracts } from "../Contracts/ExplorerContracts";
import * as ViewModels from "../Contracts/ViewModels";
import Explorer from "./Explorer";
import { handleOpenAction } from "./OpenActions";
import AddCollectionPane from "./Panes/AddCollectionPane";
import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
describe("OpenActions", () => {
@@ -15,8 +14,7 @@ describe("OpenActions", () => {
beforeEach(() => {
explorer = {} as Explorer;
explorer.addCollectionPane = {} as AddCollectionPane;
explorer.addCollectionPane.open = jest.fn();
explorer.onNewCollectionClicked = jest.fn();
explorer.cassandraAddCollectionPane = {} as CassandraAddCollectionPane;
explorer.cassandraAddCollectionPane.open = jest.fn();
explorer.closeAllPanes = () => {};
@@ -90,24 +88,24 @@ describe("OpenActions", () => {
});
describe("AddCollection pane kind", () => {
it("string value should call addCollectionPane.open", () => {
it("string value should call explorer.onNewCollectionClicked", () => {
const action = {
actionType: "OpenPane",
paneKind: "AddCollection",
};
const actionHandled = handleOpenAction(action, [], explorer);
expect(explorer.addCollectionPane.open).toHaveBeenCalled();
expect(explorer.onNewCollectionClicked).toHaveBeenCalled();
});
it("enum value should call addCollectionPane.open", () => {
it("enum value should call explorer.onNewCollectionClicked", () => {
const action = {
actionType: "OpenPane",
paneKind: ActionContracts.PaneKind.AddCollection,
};
const actionHandled = handleOpenAction(action, [], explorer);
expect(explorer.addCollectionPane.open).toHaveBeenCalled();
expect(explorer.onNewCollectionClicked).toHaveBeenCalled();
});
});
});

View File

@@ -141,7 +141,7 @@ function openPane(action: ActionContracts.OpenPane, explorer: Explorer) {
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.AddCollection]
) {
explorer.closeAllPanes();
explorer.addCollectionPane.open();
explorer.onNewCollectionClicked();
} else if (
action.paneKind === ActionContracts.PaneKind.CassandraAddCollection ||
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.CassandraAddCollection]

View File

@@ -143,7 +143,6 @@
size="40"
class="collid"
data-bind="visible: databaseCreateNew, textInput: databaseId, hasFocus: firstFieldHasFocus"
aria-label="Database id"
autofocus
/>
@@ -161,7 +160,6 @@
size="40"
class="collid"
data-bind="visible: !databaseCreateNew(), textInput: databaseId, hasFocus: firstFieldHasFocus"
aria-label="Database id"
/>
<datalist id="databasesList" data-bind="foreach: databaseIds" data-bind="visible: databaseCreateNew">
@@ -246,7 +244,7 @@
placeholder="e.g., Container1"
size="40"
class="textfontclr collid"
data-bind="value: collectionId, attr: { 'aria-label': collectionIdTitle }"
data-bind="value: collectionId"
/>
</div>
@@ -352,7 +350,6 @@
attr: {
placeholder: partitionKeyPlaceholder,
required: partitionKeyVisible(),
'aria-label': partitionKeyName,
pattern: partitionKeyPattern,
title: partitionKeyTitle
}"

View File

@@ -476,7 +476,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
userContext.portalEnv,
this.isFreeTierAccount(),
this.container.isFirstResourceCreated(),
userContext.apiType,
true
);
});

View File

@@ -8,6 +8,7 @@ import {
IconButton,
IDropdownOption,
Link,
Separator,
Stack,
Text,
TooltipHost,
@@ -23,6 +24,7 @@ import { CollectionCreation, IndexingPolicies } from "../../Shared/Constants";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import { getCollectionName } from "../../Utils/APITypeUtils";
import { getUpsellMessage } from "../../Utils/PricingUtils";
import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent";
import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput";
@@ -35,6 +37,7 @@ export interface AddCollectionPanelProps {
explorer: Explorer;
closePanel: () => void;
openNotificationConsole: () => void;
databaseId?: string;
}
export interface AddCollectionPanelState {
@@ -48,7 +51,7 @@ export interface AddCollectionPanelState {
partitionKey: string;
enableDedicatedThroughput: boolean;
createMongoWildCardIndex: boolean;
useHashV1: boolean;
useHashV2: boolean;
enableAnalyticalStore: boolean;
uniqueKeys: string[];
errorMessage: string;
@@ -67,17 +70,18 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
super(props);
this.state = {
createNewDatabase: userContext.apiType !== "Tables",
createNewDatabase: userContext.apiType !== "Tables" && !this.props.databaseId,
newDatabaseId: "",
isSharedThroughputChecked: this.getSharedThroughputDefault(),
selectedDatabaseId: userContext.apiType === "Tables" ? CollectionCreation.TablesAPIDefaultDatabase : undefined,
selectedDatabaseId:
userContext.apiType === "Tables" ? CollectionCreation.TablesAPIDefaultDatabase : this.props.databaseId,
collectionId: "",
enableIndexing: true,
isSharded: userContext.apiType !== "Tables",
partitionKey: "",
enableDedicatedThroughput: false,
createMongoWildCardIndex: true,
useHashV1: false,
useHashV2: false,
enableAnalyticalStore: false,
uniqueKeys: [],
errorMessage: "",
@@ -100,13 +104,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{!this.state.errorMessage && this.isFreeTierAccount() && (
<PanelInfoErrorComponent
message={getUpsellMessage(
userContext.portalEnv,
true,
this.props.explorer.isFirstResourceCreated(),
userContext.apiType,
true
)}
message={getUpsellMessage(userContext.portalEnv, true, this.props.explorer.isFirstResourceCreated(), true)}
messageType="info"
showErrorDetails={false}
openNotificationConsole={this.props.openNotificationConsole}
@@ -120,13 +118,15 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
Database id
Database {userContext.apiType === "Mongo" ? "name" : "id"}
</Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content="A database is analogous to a namespace. It is the unit of management for a set of containers."
content={`A database is analogous to a namespace. It is the unit of management for a set of ${getCollectionName(
true
).toLocaleLowerCase()}.`}
>
<Icon iconName="InfoSolid" className="panelInfoIcon" />
<Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost>
</Stack>
@@ -140,7 +140,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
type="radio"
role="radio"
id="databaseCreateNew"
data-test="addCollection-createNewDatabase"
tabIndex={0}
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
/>
@@ -154,8 +153,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
name="databaseType"
type="radio"
role="radio"
id="databaseUseExisting"
data-test="addCollection-existingDatabase"
tabIndex={0}
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
/>
@@ -166,8 +163,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
<Stack className="panelGroupSpacing">
<input
name="newDatabaseId"
id="databaseId"
data-test="addCollection-newDatabaseId"
id="newDatabaseId"
aria-required
required
type="text"
@@ -177,7 +173,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
placeholder="Type a new database id"
size={40}
className="panelTextField"
aria-label="Database id"
aria-label="New database id"
autoFocus
value={this.state.newDatabaseId}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
@@ -188,7 +184,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{!this.isServerlessAccount() && (
<Stack horizontal>
<Checkbox
label="Provision database throughput"
label={`Share throughput across ${getCollectionName(true).toLocaleLowerCase()}`}
checked={this.state.isSharedThroughputChecked}
styles={{
text: { fontSize: 12 },
@@ -201,9 +197,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
/>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content="Provisioned throughput at the database level will be shared across all containers within the database."
content={`Throughput configured at the database level will be shared across all ${getCollectionName(
true
).toLocaleLowerCase()} within the database.`}
>
<Icon iconName="InfoSolid" className="panelInfoIcon" />
<Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost>
</Stack>
)}
@@ -216,6 +214,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
isDatabase={true}
isAutoscaleSelected={this.isNewDatabaseAutoscale}
throughput={this.newDatabaseThroughput}
isSharded={this.state.isSharded}
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
onCostAcknowledgeChange={(isAcknowledge: boolean) => (this.isCostAcknowledged = isAcknowledge)}
@@ -232,38 +231,40 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
onChange={(event: React.FormEvent<HTMLDivElement>, database: IDropdownOption) =>
this.setState({ selectedDatabaseId: database.key as string })
}
defaultSelectedKey={this.props.databaseId}
responsiveMode={999}
/>
)}
<Separator className="panelSeparator" />
</Stack>
<Stack>
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
{`${this.getCollectionName()} id`}
{`${getCollectionName()} ${userContext.apiType === "Mongo" ? "name" : "id"}`}
</Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content="Unique identifier for the container and used for id-based routing through REST and all SDKs."
content={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
>
<Icon iconName="InfoSolid" className="panelInfoIcon" />
<Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost>
</Stack>
<input
name="collectionId"
id="containerId"
data-test="addCollection-collectionId"
id="collectionId"
type="text"
aria-required
required
autoComplete="off"
pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
placeholder={`e.g., ${this.getCollectionName()}1`}
placeholder={`e.g., ${getCollectionName()}1`}
size={40}
className="panelTextField"
aria-label={`${this.getCollectionName()} id`}
aria-label={`${getCollectionName()} id`}
value={this.state.collectionId}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
this.setState({ collectionId: event.target.value })
@@ -322,13 +323,15 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
Sharding options
Sharding
</Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content="Unique identifier for the container and used for id-based routing through REST and all SDKs."
content={
"Sharded collections split your data across many replica sets (shards) to achieve unlimited scalability. Sharded collections require choosing a shard key (field) to evenly distribute your data."
}
>
<Icon iconName="InfoSolid" className="panelInfoIcon" />
<Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost>
</Stack>
@@ -373,18 +376,15 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content={`The ${this.getPartitionKeyName()} is used to automatically partition data among
multiple servers for scalability. Choose a JSON property name that has a wide range of values and is
likely to have evenly distributed access patterns.`}
content={this.getPartitionKeyTooltipText()}
>
<Icon iconName="InfoSolid" className="panelInfoIcon" />
<Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost>
</Stack>
<input
type="text"
id="addCollection-partitionKeyValue"
data-test="addCollection-partitionKeyValue"
aria-required
required
size={40}
@@ -394,9 +394,17 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
pattern={userContext.apiType === "Gremlin" ? "^/[^/]*" : ".*"}
title={userContext.apiType === "Gremlin" ? "May not use composite partition key" : ""}
value={this.state.partitionKey}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
this.setState({ partitionKey: event.target.value })
}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
if (
userContext.apiType !== "Mongo" &&
this.state.partitionKey === "" &&
!event.target.value.startsWith("/")
) {
this.setState({ partitionKey: "/" + event.target.value });
} else {
this.setState({ partitionKey: event.target.value });
}
}}
/>
</Stack>
)}
@@ -404,7 +412,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{!this.isServerlessAccount() && !this.state.createNewDatabase && this.isSelectedDatabaseSharedThroughput() && (
<Stack horizontal verticalAlign="center">
<Checkbox
label={`Provision dedicated throughput for this ${this.getCollectionName()}`}
label={`Provision dedicated throughput for this ${getCollectionName().toLocaleLowerCase()}`}
checked={this.state.enableDedicatedThroughput}
styles={{
text: { fontSize: 12 },
@@ -417,12 +425,14 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
/>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content="You can optionally provision dedicated throughput for a container within a database that has throughput
provisioned. This dedicated throughput amount will not be shared with other containers in the database and
content={`You can optionally provision dedicated throughput for a ${getCollectionName().toLocaleLowerCase()} within a database that has throughput
provisioned. This dedicated throughput amount will not be shared with other ${getCollectionName(
true
).toLocaleLowerCase()} in the database and
does not count towards the throughput you provisioned for the database. This throughput amount will be
billed in addition to the throughput amount you provisioned at the database level."
billed in addition to the throughput amount you provisioned at the database level.`}
>
<Icon iconName="InfoSolid" className="panelInfoIcon" />
<Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost>
</Stack>
)}
@@ -435,6 +445,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
isDatabase={false}
isAutoscaleSelected={this.isCollectionAutoscale}
throughput={this.collectionThroughput}
isSharded={this.state.isSharded}
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)}
onCostAcknowledgeChange={(isAcknowledged: boolean) => {
@@ -455,7 +466,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
creating a unique key policy when a container is created, you ensure the uniqueness of one or more values
per partition key."
>
<Icon iconName="InfoSolid" className="panelInfoIcon" />
<Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost>
</Stack>
@@ -508,134 +519,129 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</Stack>
)}
<CollapsibleSectionComponent title="Advanced" isExpandedByDefault={false}>
<Stack className="panelGroupSpacing">
{this.props.explorer.isEnableMongoCapabilityPresent() && (
<Stack>
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
Indexing
</Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content="By default, only the field _id is indexed. Creating a wildcard index on all fields will quickly optimize
query performance and is recommended during development."
>
<Icon iconName="InfoSolid" className="panelInfoIcon" />
</TooltipHost>
</Stack>
{userContext.apiType !== "Tables" && (
<CollapsibleSectionComponent
title="Advanced"
isExpandedByDefault={false}
onExpand={() => {
TelemetryProcessor.traceOpen(Action.ExpandAddCollectionPaneAdvancedSection);
this.scrollToAdvancedSection();
}}
>
<Stack className="panelGroupSpacing" id="collapsibleSectionContent">
{this.props.explorer.isEnableMongoCapabilityPresent() && (
<Stack className="panelGroupSpacing">
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
Indexing
</Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content="The _id field is indexed by default. Creating a wildcard index for all fields will optimize queries and is recommended for development."
>
<Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost>
</Stack>
<Checkbox
label="Create a Wildcard Index on all fields"
checked={this.state.createMongoWildCardIndex}
styles={{
text: { fontSize: 12 },
checkbox: { width: 12, height: 12 },
label: { padding: 0, alignItems: "center" },
}}
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
this.setState({ createMongoWildCardIndex: isChecked })
}
/>
</Stack>
)}
{userContext.apiType === "SQL" && (
<Checkbox
label="Create a Wildcard Index on all fields"
checked={this.state.createMongoWildCardIndex}
label="My partition key is larger than 100 bytes"
checked={this.state.useHashV2}
styles={{
text: { fontSize: 12 },
checkbox: { width: 12, height: 12 },
label: { padding: 0, alignItems: "center" },
}}
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
this.setState({ createMongoWildCardIndex: isChecked })
this.setState({ useHashV2: isChecked })
}
/>
</Stack>
)}
)}
{userContext.apiType === "SQL" && (
<Stack className="panelGroupSpacing">
<Stack horizontal verticalAlign="start">
<Checkbox
checked={this.state.useHashV1}
styles={{
checkbox: { width: 12, height: 12 },
label: { padding: 0, margin: "4px 4px 0 0" },
}}
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
this.setState({ useHashV1: isChecked })
}
/>
<Text variant="small" style={{ lineHeight: "20px" }}>
My application uses an older Cosmos .NET or Java SDK version (.NET V1 or Java V2)
</Text>
</Stack>
<Text variant="small">
To ensure compatibility with older SDKs, the created container will use a legacy partitioning scheme
that supports partition key values of size up to 100 bytes.{" "}
<Link target="_blank" href="https://aka.ms/cosmosdb/pkv2">
Learn more
</Link>
</Text>
</Stack>
)}
{this.shouldShowAnalyticalStoreOptions() && (
<Stack className="panelGroupSpacing">
<Stack horizontal>
<Text className="panelTextBold" variant="small">
Analytical store
</Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content="Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads. Learn more"
>
<Icon iconName="InfoSolid" className="panelInfoIcon" />
</TooltipHost>
</Stack>
<Stack horizontal verticalAlign="center">
<input
className="panelRadioBtn"
checked={this.state.enableAnalyticalStore}
disabled={!this.isSynapseLinkEnabled()}
aria-label="Enable analytical store"
aria-checked={this.state.enableAnalyticalStore}
name="analyticalStore"
type="radio"
role="radio"
id="enableAnalyticalStoreBtn"
tabIndex={0}
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">On</span>
<input
className="panelRadioBtn"
checked={!this.state.enableAnalyticalStore}
disabled={!this.isSynapseLinkEnabled()}
aria-label="Disable analytical store"
aria-checked={!this.state.enableAnalyticalStore}
name="analyticalStore"
type="radio"
role="radio"
id="disableAnalyticalStoreBtn"
tabIndex={0}
onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">Off</span>
</Stack>
{!this.isSynapseLinkEnabled() && (
<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.{" "}
<Link href="https://aka.ms/cosmosdb-synapselink" target="_blank">
Learn more
</Link>
{this.shouldShowAnalyticalStoreOptions() && (
<Stack className="panelGroupSpacing">
<Stack horizontal>
<Text className="panelTextBold" variant="small">
Analytical store
</Text>
<DefaultButton
text="Enable"
onClick={() => this.props.explorer.openEnableSynapseLinkDialog()}
style={{ height: 27, width: 80 }}
styles={{ label: { fontSize: 12 } }}
/>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content={this.getAnalyticalStorageTooltipContent()}
>
<Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost>
</Stack>
)}
</Stack>
)}
</Stack>
</CollapsibleSectionComponent>
<Stack horizontal verticalAlign="center">
<input
className="panelRadioBtn"
checked={this.state.enableAnalyticalStore}
disabled={!this.isSynapseLinkEnabled()}
aria-label="Enable analytical store"
aria-checked={this.state.enableAnalyticalStore}
name="analyticalStore"
type="radio"
role="radio"
id="enableAnalyticalStoreBtn"
tabIndex={0}
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">On</span>
<input
className="panelRadioBtn"
checked={!this.state.enableAnalyticalStore}
disabled={!this.isSynapseLinkEnabled()}
aria-label="Disable analytical store"
aria-checked={!this.state.enableAnalyticalStore}
name="analyticalStore"
type="radio"
role="radio"
id="disableAnalyticalStoreBtn"
tabIndex={0}
onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">Off</span>
</Stack>
{!this.isSynapseLinkEnabled() && (
<Stack className="panelGroupSpacing">
<Text variant="small">
Azure Synapse Link is required for creating an analytical store{" "}
{getCollectionName().toLocaleLowerCase()}. Enable Synapse Link for this Cosmos DB account.{" "}
<Link href="https://aka.ms/cosmosdb-synapselink" target="_blank">
Learn more
</Link>
</Text>
<DefaultButton
text="Enable"
onClick={() => this.props.explorer.openEnableSynapseLinkDialog()}
style={{ height: 27, width: 80 }}
styles={{ label: { fontSize: 12 } }}
/>
</Stack>
)}
</Stack>
)}
</Stack>
</CollapsibleSectionComponent>
)}
</div>
<PanelFooterComponent buttonLabel="OK" />
@@ -652,24 +658,10 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
}));
}
private getCollectionName(): string {
switch (userContext.apiType) {
case "SQL":
return "Container";
case "Mongo":
return "Collection";
case "Cassandra":
case "Tables":
return "Table";
case "Gremlin":
return "Graph";
default:
throw new Error(`Unsupported default experience type: ${userContext.apiType}`);
}
}
private getPartitionKeyName(isLowerCase?: boolean): string {
const partitionKeyName = userContext.apiType === "Mongo" ? "Shard key" : "Partition key";
private getPartitionKeyName(): string {
return userContext.apiType === "Mongo" ? "Shard key" : "Partition key";
return isLowerCase ? partitionKeyName.toLocaleLowerCase() : partitionKeyName;
}
private getPartitionKeyPlaceHolder(): string {
@@ -778,6 +770,34 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
: "Indexing will be turned off. Recommended if you don't need to run queries or only have key value operations.";
}
private getPartitionKeyTooltipText(): string {
if (userContext.apiType === "Mongo") {
return "The shard key (field) is used to split your data across many replica sets (shards) to achieve unlimited scalability. Its critical to choose a field that will evenly distribute your data.";
}
let tooltipText = `The ${this.getPartitionKeyName(
true
)} 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.`;
if (userContext.apiType === "SQL") {
tooltipText += " For small read-heavy workloads or write-heavy workloads of any size, id is often a good choice.";
}
return tooltipText;
}
private getAnalyticalStorageTooltipContent(): JSX.Element {
return (
<Text variant="small">
Enable analytical store capability to perform near real-time analytics on your operational data, without
impacting the performance of transactional workloads.{" "}
<Link target="_blank" href="https://aka.ms/analytical-store-overview">
Learn more
</Link>
</Text>
);
}
private shouldShowCollectionThroughputInput(): boolean {
if (this.isServerlessAccount()) {
return false;
@@ -883,6 +903,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
return false;
}
if (throughput > CollectionCreation.MaxRUPerPartition && !this.state.isSharded) {
this.setState({ errorMessage: "Unsharded collections support up to 10,000 RUs" });
return false;
}
if (
userContext.apiType === "Gremlin" &&
(this.state.partitionKey === "/id" || this.state.partitionKey === "/label")
@@ -909,6 +934,10 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
return Constants.AnalyticalStorageTtl.Disabled;
}
private scrollToAdvancedSection(): void {
document.getElementById("collapsibleSectionContent")?.scrollIntoView();
}
private async submit(event: React.FormEvent<HTMLFormElement>): Promise<void> {
event.preventDefault();
@@ -927,7 +956,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
}
const uniqueKeyPolicy: DataModels.UniqueKeyPolicy = this.parseUniqueKeys();
const partitionKeyVersion = this.state.useHashV1 ? undefined : 2;
const partitionKeyVersion = this.state.useHashV2 ? 2 : undefined;
const partitionKey: DataModels.PartitionKey = partitionKeyString
? {
paths: [partitionKeyString],

View File

@@ -238,7 +238,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
userContext.portalEnv,
this.isFreeTierAccount(),
this.container.isFirstResourceCreated(),
userContext.apiType,
false
);
});

View File

@@ -15,11 +15,8 @@ import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import * as PricingUtils from "../../../Utils/PricingUtils";
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
import Explorer from "../../Explorer";
import {
GenericRightPaneComponent,
GenericRightPaneProps,
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
export interface AddDatabasePaneProps {
explorer: Explorer;
@@ -85,7 +82,6 @@ export const AddDatabasePanelF: FunctionComponent<AddDatabasePaneProps> = ({
userContext.portalEnv,
isFreeTierAccount,
container.isFirstResourceCreated(),
userContext.apiType,
false
);
@@ -254,8 +250,8 @@ export const AddDatabasePanelF: FunctionComponent<AddDatabasePaneProps> = ({
[]
);
const genericPaneProps: GenericRightPaneProps = {
container,
const genericPaneProps: RightPaneFormProps = {
expandConsole: container.expandConsole,
formError: formErrors,
formErrorDetail: formErrorsDetails,
id: "add-database-inputs",
@@ -267,7 +263,7 @@ export const AddDatabasePanelF: FunctionComponent<AddDatabasePaneProps> = ({
};
return (
<GenericRightPaneComponent {...genericPaneProps}>
<RightPaneForm {...genericPaneProps}>
<form style={{ height: "100%" }}>
<div className="paneContentContainer" role="dialog" aria-labelledby="databaseTitle">
{showUpsellMessage && formErrors === "" && (
@@ -327,6 +323,7 @@ export const AddDatabasePanelF: FunctionComponent<AddDatabasePaneProps> = ({
<ThroughputInput
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container?.isFirstResourceCreated()}
isDatabase={true}
isSharded={databaseCreateNewShared}
isAutoscaleSelected={isAutoPilotSelected}
throughput={throughput}
setThroughputValue={(throughput: number) => setThroughput(throughput)}
@@ -348,6 +345,6 @@ export const AddDatabasePanelF: FunctionComponent<AddDatabasePaneProps> = ({
</div>
</div>
</form>
</GenericRightPaneComponent>
</RightPaneForm>
);
};

View File

@@ -1,505 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AddDatabasePane Pane should render Default properly 1`] = `
<GenericRightPaneComponent
container={
Explorer {
"_closeModalDialog": [Function],
"_closeSynapseLinkModalDialog": [Function],
"_isAfecFeatureRegistered": [Function],
"_isInitializingNotebooks": false,
"_panes": Array [
AddDatabasePane {
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"container": [Circular],
"costsVisible": [Function],
"databaseCreateNewShared": [Function],
"databaseId": [Function],
"databaseIdLabel": [Function],
"databaseIdPlaceHolder": [Function],
"databaseIdTooltipText": [Function],
"databaseLevelThroughputTooltipText": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "adddatabasepane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFreeTierAccount": [Function],
"isTemplateReady": [Function],
"maxAutoPilotThroughputSet": [Function],
"maxThroughputRU": [Function],
"maxThroughputRUText": [Function],
"minThroughputRU": [Function],
"onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function],
"ruToolTipText": [Function],
"showUpsellMessage": [Function],
"throughput": [Function],
"throughputRangeText": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"upsellAnchorText": [Function],
"upsellAnchorUrl": [Function],
"upsellMessage": [Function],
"upsellMessageAriaLabel": [Function],
"visible": [Function],
},
AddCollectionPane {
"_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function],
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"collectionId": [Function],
"collectionIdTitle": [Function],
"collectionWithThroughputInShared": [Function],
"collectionWithThroughputInSharedTitle": [Function],
"container": [Circular],
"costsVisible": [Function],
"databaseCreateNew": [Function],
"databaseCreateNewShared": [Function],
"databaseHasSharedOffer": [Function],
"databaseId": [Function],
"databaseIds": [Function],
"dedicatedRequestUnitsUsageCost": [Function],
"displayCollectionThroughput": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"formWarnings": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "addcollectionpane",
"isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFixedStorageSelected": [Function],
"isFreeTierAccount": [Function],
"isNonTableApi": [Function],
"isPreferredApiTable": [Function],
"isSharedAutoPilotSelected": [Function],
"isSynapseLinkSupported": [Function],
"isSynapseLinkUpdating": [Function],
"isTemplateReady": [Function],
"isTryCosmosDBSubscription": [Function],
"isUnlimitedStorageSelected": [Function],
"largePartitionKey": [Function],
"lowerCasePartitionKeyName": [Function],
"maxCollectionsReached": [Function],
"maxCollectionsReachedMessage": [Function],
"maxThroughputRU": [Function],
"minThroughputRU": [Function],
"onMoreDetailsKeyPress": [Function],
"partitionKey": [Function],
"partitionKeyName": [Function],
"partitionKeyPattern": [Function],
"partitionKeyPlaceholder": [Function],
"partitionKeyTitle": [Function],
"partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function],
"ruToolTipText": [Function],
"sharedAutoPilotThroughput": [Function],
"sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function],
"showAnalyticalStore": [Function],
"showEnableSynapseLink": [Function],
"showIndexingOptionsForSharedThroughput": [Function],
"showUpsellMessage": [Function],
"storage": [Function],
"throughputDatabase": [Function],
"throughputMultiPartition": [Function],
"throughputRangeText": [Function],
"throughputSinglePartition": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"ttl90DaysEnabled": [Function],
"uniqueKeys": [Function],
"uniqueKeysPlaceholder": [Function],
"uniqueKeysVisible": [Function],
"upsellAnchorText": [Function],
"upsellAnchorUrl": [Function],
"upsellMessage": [Function],
"upsellMessageAriaLabel": [Function],
"useIndexingForSharedThroughput": [Function],
"visible": [Function],
},
GraphStylingPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"graphConfigUIData": Object {
"nodeCaptionChoice": [Function],
"nodeColorKeyChoice": [Function],
"nodeIconChoice": [Function],
"nodeIconSet": [Function],
"nodeProperties": [Function],
"nodePropertiesWithNone": [Function],
"showNeighborType": [Function],
},
"id": "graphstylingpane",
"isExecuting": [Function],
"isTemplateReady": [Function],
"title": [Function],
"visible": [Function],
},
CassandraAddCollectionPane {
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"container": [Circular],
"costsVisible": [Function],
"createTableQuery": [Function],
"dedicateTableThroughput": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFreeTierAccount": [Function],
"isSharedAutoPilotSelected": [Function],
"isTemplateReady": [Function],
"keyspaceCreateNew": [Function],
"keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function],
"keyspaceIds": [Function],
"keyspaceOffers": Map {},
"keyspaceThroughput": [Function],
"maxThroughputRU": [Function],
"minThroughputRU": [Function],
"requestUnitsUsageCostDedicated": [Function],
"requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function],
"sharedAutoPilotThroughput": [Function],
"sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function],
"sharedThroughputSpendAckVisible": [Function],
"tableId": [Function],
"throughput": [Function],
"throughputRangeText": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"userTableQuery": [Function],
"visible": [Function],
},
],
"_refreshSparkEnabledStateForAccount": [Function],
"_resetNotebookWorkspace": [Function],
"addCollectionPane": AddCollectionPane {
"_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function],
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"collectionId": [Function],
"collectionIdTitle": [Function],
"collectionWithThroughputInShared": [Function],
"collectionWithThroughputInSharedTitle": [Function],
"container": [Circular],
"costsVisible": [Function],
"databaseCreateNew": [Function],
"databaseCreateNewShared": [Function],
"databaseHasSharedOffer": [Function],
"databaseId": [Function],
"databaseIds": [Function],
"dedicatedRequestUnitsUsageCost": [Function],
"displayCollectionThroughput": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"formWarnings": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "addcollectionpane",
"isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFixedStorageSelected": [Function],
"isFreeTierAccount": [Function],
"isNonTableApi": [Function],
"isPreferredApiTable": [Function],
"isSharedAutoPilotSelected": [Function],
"isSynapseLinkSupported": [Function],
"isSynapseLinkUpdating": [Function],
"isTemplateReady": [Function],
"isTryCosmosDBSubscription": [Function],
"isUnlimitedStorageSelected": [Function],
"largePartitionKey": [Function],
"lowerCasePartitionKeyName": [Function],
"maxCollectionsReached": [Function],
"maxCollectionsReachedMessage": [Function],
"maxThroughputRU": [Function],
"minThroughputRU": [Function],
"onMoreDetailsKeyPress": [Function],
"partitionKey": [Function],
"partitionKeyName": [Function],
"partitionKeyPattern": [Function],
"partitionKeyPlaceholder": [Function],
"partitionKeyTitle": [Function],
"partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function],
"ruToolTipText": [Function],
"sharedAutoPilotThroughput": [Function],
"sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function],
"showAnalyticalStore": [Function],
"showEnableSynapseLink": [Function],
"showIndexingOptionsForSharedThroughput": [Function],
"showUpsellMessage": [Function],
"storage": [Function],
"throughputDatabase": [Function],
"throughputMultiPartition": [Function],
"throughputRangeText": [Function],
"throughputSinglePartition": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"ttl90DaysEnabled": [Function],
"uniqueKeys": [Function],
"uniqueKeysPlaceholder": [Function],
"uniqueKeysVisible": [Function],
"upsellAnchorText": [Function],
"upsellAnchorUrl": [Function],
"upsellMessage": [Function],
"upsellMessageAriaLabel": [Function],
"useIndexingForSharedThroughput": [Function],
"visible": [Function],
},
"addCollectionText": [Function],
"addDatabasePane": AddDatabasePane {
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"container": [Circular],
"costsVisible": [Function],
"databaseCreateNewShared": [Function],
"databaseId": [Function],
"databaseIdLabel": [Function],
"databaseIdPlaceHolder": [Function],
"databaseIdTooltipText": [Function],
"databaseLevelThroughputTooltipText": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "adddatabasepane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFreeTierAccount": [Function],
"isTemplateReady": [Function],
"maxAutoPilotThroughputSet": [Function],
"maxThroughputRU": [Function],
"maxThroughputRUText": [Function],
"minThroughputRU": [Function],
"onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function],
"ruToolTipText": [Function],
"showUpsellMessage": [Function],
"throughput": [Function],
"throughputRangeText": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"upsellAnchorText": [Function],
"upsellAnchorUrl": [Function],
"upsellMessage": [Function],
"upsellMessageAriaLabel": [Function],
"visible": [Function],
},
"addDatabaseText": [Function],
"arcadiaToken": [Function],
"canExceedMaximumValue": [Function],
"canSaveQueries": [Function],
"cassandraAddCollectionPane": CassandraAddCollectionPane {
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"container": [Circular],
"costsVisible": [Function],
"createTableQuery": [Function],
"dedicateTableThroughput": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFreeTierAccount": [Function],
"isSharedAutoPilotSelected": [Function],
"isTemplateReady": [Function],
"keyspaceCreateNew": [Function],
"keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function],
"keyspaceIds": [Function],
"keyspaceOffers": Map {},
"keyspaceThroughput": [Function],
"maxThroughputRU": [Function],
"minThroughputRU": [Function],
"requestUnitsUsageCostDedicated": [Function],
"requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function],
"sharedAutoPilotThroughput": [Function],
"sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function],
"sharedThroughputSpendAckVisible": [Function],
"tableId": [Function],
"throughput": [Function],
"throughputRangeText": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"userTableQuery": [Function],
"visible": [Function],
},
"clickHostedAccountSwitch": [Function],
"clickHostedDirectorySwitch": [Function],
"closeDialog": undefined,
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"collectionCreationDefaults": Object {
"storage": "100",
"throughput": Object {
"fixed": 400,
"shared": 400,
"unlimited": 400,
"unlimitedmax": 1000000,
"unlimitedmin": 400,
},
},
"collectionTitle": [Function],
"collectionTreeNodeAltText": [Function],
"commandBarComponentAdapter": CommandBarComponentAdapter {
"container": [Circular],
"isNotebookTabActive": [Function],
"parameters": [Function],
"tabsButtons": Array [],
},
"databaseAccount": [Function],
"databases": [Function],
"defaultExperience": [Function],
"deleteCollectionText": [Function],
"deleteDatabaseText": [Function],
"graphStylingPane": GraphStylingPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"graphConfigUIData": Object {
"nodeCaptionChoice": [Function],
"nodeColorKeyChoice": [Function],
"nodeIconChoice": [Function],
"nodeIconSet": [Function],
"nodeProperties": [Function],
"nodePropertiesWithNone": [Function],
"showNeighborType": [Function],
},
"id": "graphstylingpane",
"isExecuting": [Function],
"isTemplateReady": [Function],
"title": [Function],
"visible": [Function],
},
"hasStorageAnalyticsAfecFeature": [Function],
"isAccountReady": [Function],
"isAutoscaleDefaultEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isGitHubPaneEnabled": [Function],
"isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function],
"isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
"isPublishNotebookPaneEnabled": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function],
"isSchemaEnabled": [Function],
"isServerlessEnabled": [Function],
"isSparkEnabled": [Function],
"isSparkEnabledForAccount": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
"memoryUsageInfo": [Function],
"notebookBasePath": [Function],
"notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function],
"openDialog": undefined,
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
"container": [Circular],
},
"refreshDatabaseAccount": [Function],
"refreshNotebookList": [Function],
"refreshTreeTitle": [Function],
"resourceTokenCollection": [Function],
"resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function],
"resourceTokenPartitionKey": [Function],
"resourceTree": ResourceTreeAdapter {
"container": [Circular],
"copyNotebook": [Function],
"databaseCollectionIdMap": Map {},
"koSubsCollectionIdMap": Map {},
"koSubsDatabaseIdMap": Map {},
"parameters": [Function],
},
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
"container": [Circular],
"parameters": [Function],
},
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
"setNotificationConsoleData": undefined,
"signInAad": [Function],
"sparkClusterConnectionInfo": [Function],
"splitter": Splitter {
"bounds": Object {
"max": 400,
"min": 240,
},
"direction": "vertical",
"isCollapsed": [Function],
"leftSideId": "resourcetree",
"onResizeStart": [Function],
"onResizeStop": [Function],
"splitterId": "h_splitter1",
},
"tabsManager": TabsManager {
"activeTab": [Function],
"openedTabs": [Function],
},
"toggleLeftPaneExpandedKeyPress": [Function],
}
}
<RightPaneForm
expandConsole={[Function]}
formError=""
id="add-database-inputs"
isExecuting={false}
@@ -598,6 +101,7 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
<ThroughputInput
isAutoscaleSelected={false}
isDatabase={true}
isSharded={true}
onCostAcknowledgeChange={[Function]}
setIsAutoscale={[Function]}
setThroughputValue={[Function]}
@@ -608,5 +112,5 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
</div>
</div>
</form>
</GenericRightPaneComponent>
</RightPaneForm>
`;

View File

@@ -1,194 +0,0 @@
import ko from "knockout";
import { IDropdownOption } from "office-ui-fabric-react";
import * as React from "react";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import { HttpStatusCodes } from "../../Common/Constants";
import { getErrorMessage, handleError } from "../../Common/ErrorHandlingUtils";
import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService";
import { IPinnedRepo, JunoClient } from "../../Juno/JunoClient";
import * as GitHubUtils from "../../Utils/GitHubUtils";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import Explorer from "../Explorer";
import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem";
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPane/CopyNotebookPaneComponent";
import { RightPaneForm, RightPaneFormProps } from "./RightPaneForm/RightPaneForm";
interface Location {
type: "MyNotebooks" | "GitHub";
// GitHub
owner?: string;
repo?: string;
branch?: string;
}
export class CopyNotebookPaneAdapter implements ReactAdapter {
private static readonly BranchNameWhiteSpace = " ";
parameters: ko.Observable<number>;
private isOpened: boolean;
private isExecuting: boolean;
private formError: string;
private formErrorDetail: string;
private name: string;
private content: string;
private pinnedRepos: IPinnedRepo[];
private selectedLocation: Location;
constructor(
private container: Explorer,
private junoClient: JunoClient,
private gitHubOAuthService: GitHubOAuthService
) {
this.parameters = ko.observable(Date.now());
this.reset();
this.triggerRender();
}
public renderComponent(): JSX.Element {
if (!this.isOpened) {
return undefined;
}
const genericPaneProps: RightPaneFormProps = {
container: this.container,
formError: this.formError,
formErrorDetail: this.formErrorDetail,
id: "copynotebookpane",
isExecuting: this.isExecuting,
title: "Copy notebook",
submitButtonText: "OK",
onClose: () => this.close(),
onSubmit: () => this.submit(),
};
const copyNotebookPaneProps: CopyNotebookPaneProps = {
name: this.name,
pinnedRepos: this.pinnedRepos,
onDropDownChange: this.onDropDownChange,
};
return (
<RightPaneForm {...genericPaneProps}>
<CopyNotebookPaneComponent {...copyNotebookPaneProps} />
</RightPaneForm>
);
}
public triggerRender(): void {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
public async open(name: string, content: string): Promise<void> {
this.name = name;
this.content = content;
this.isOpened = true;
this.triggerRender();
if (this.gitHubOAuthService.isLoggedIn()) {
const response = await this.junoClient.getPinnedRepos(this.gitHubOAuthService.getTokenObservable()()?.scope);
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
handleError(`Received HTTP ${response.status} when fetching pinned repos`, "CopyNotebookPaneAdapter/submit");
}
if (response.data?.length > 0) {
this.pinnedRepos = response.data;
this.triggerRender();
}
}
}
public close(): void {
this.reset();
this.triggerRender();
}
public async submit(): Promise<void> {
let destination: string = this.selectedLocation?.type;
let clearMessage: () => void;
this.isExecuting = true;
this.triggerRender();
try {
if (!this.selectedLocation) {
throw new Error(`No location selected`);
}
if (this.selectedLocation.type === "GitHub") {
destination = `${destination} - ${GitHubUtils.toRepoFullName(
this.selectedLocation.owner,
this.selectedLocation.repo
)} - ${this.selectedLocation.branch}`;
}
clearMessage = NotificationConsoleUtils.logConsoleProgress(`Copying ${this.name} to ${destination}`);
const notebookContentItem = await this.copyNotebook(this.selectedLocation);
if (!notebookContentItem) {
throw new Error(`Failed to upload ${this.name}`);
}
NotificationConsoleUtils.logConsoleInfo(`Successfully copied ${this.name} to ${destination}`);
} catch (error) {
const errorMessage = getErrorMessage(error);
this.formError = `Failed to copy ${this.name} to ${destination}`;
this.formErrorDetail = `${errorMessage}`;
handleError(errorMessage, "CopyNotebookPaneAdapter/submit", this.formError);
return;
} finally {
clearMessage && clearMessage();
this.isExecuting = false;
this.triggerRender();
}
this.close();
}
private copyNotebook = async (location: Location): Promise<NotebookContentItem> => {
let parent: NotebookContentItem;
switch (location.type) {
case "MyNotebooks":
parent = {
name: ResourceTreeAdapter.MyNotebooksTitle,
path: this.container.getNotebookBasePath(),
type: NotebookContentItemType.Directory,
};
break;
case "GitHub":
parent = {
name: ResourceTreeAdapter.GitHubReposTitle,
path: GitHubUtils.toContentUri(
this.selectedLocation.owner,
this.selectedLocation.repo,
this.selectedLocation.branch,
""
),
type: NotebookContentItemType.Directory,
};
break;
default:
throw new Error(`Unsupported location type ${location.type}`);
}
return this.container.uploadFile(this.name, this.content, parent);
};
private onDropDownChange = (_: React.FormEvent<HTMLDivElement>, option?: IDropdownOption): void => {
this.selectedLocation = option?.data;
};
private reset = (): void => {
this.isOpened = false;
this.isExecuting = false;
this.formError = undefined;
this.formErrorDetail = undefined;
this.name = undefined;
this.content = undefined;
this.pinnedRepos = undefined;
this.selectedLocation = undefined;
};
}

View File

@@ -131,7 +131,6 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
};
const genericPaneProps: GenericRightPaneProps = {
container,
formError,
formErrorDetail,
id: "copynotebookpane",
@@ -140,6 +139,7 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
submitButtonText: "OK",
onClose: closePanel,
onSubmit: () => submit(),
expandConsole: () => container.expandConsole(),
};
const copyNotebookPaneProps: CopyNotebookPaneProps = {

View File

@@ -9,6 +9,7 @@ import { DefaultExperienceUtility } from "../../../Shared/DefaultExperienceUtili
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../UserContext";
import { getCollectionName } from "../../../Utils/APITypeUtils";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
import Explorer from "../../Explorer";
import {
@@ -17,14 +18,12 @@ import {
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
export interface DeleteCollectionConfirmationPaneProps {
explorer: Explorer;
collectionName: string;
closePanel: () => void;
}
export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectionConfirmationPaneProps> = ({
explorer,
closePanel,
collectionName,
}: DeleteCollectionConfirmationPaneProps) => {
const [deleteCollectionFeedback, setDeleteCollectionFeedback] = useState<string>("");
const [inputCollectionName, setInputCollectionName] = useState<string>("");
@@ -34,6 +33,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
const shouldRecordFeedback = (): boolean => {
return explorer.isLastCollection() && !explorer.isSelectedDatabaseShared();
};
const collectionName = getCollectionName().toLocaleLowerCase();
const paneTitle = "Delete " + collectionName;
const submit = async (): Promise<void> => {
const collection = explorer.findSelectedCollection();
@@ -101,7 +101,6 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
}
};
const genericPaneProps: GenericRightPaneProps = {
container: explorer,
formError: formError,
formErrorDetail: formError,
id: "deleteCollectionpane",
@@ -110,6 +109,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
submitButtonText: "OK",
onClose: closePanel,
onSubmit: submit,
expandConsole: () => explorer.expandConsole(),
};
return (
<GenericRightPaneComponent {...genericPaneProps}>

View File

@@ -16,16 +16,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
}
>
<GenericRightPaneComponent
container={
Object {
"findSelectedCollection": [Function],
"isLastCollection": [Function],
"isSelectedDatabaseShared": [Function],
"refreshAllDatabases": [Function],
"selectedCollectionId": [Function],
"selectedNode": [Function],
}
}
expandConsole={[Function]}
formError=""
formErrorDetail=""
id="deleteCollectionpane"

View File

@@ -1,15 +1,13 @@
import { mount } from "enzyme";
import React from "react";
import Explorer from "../../Explorer";
import StoredProcedure from "../../Tree/StoredProcedure";
import { ExecuteSprocParamsPane } from "./ExecuteSprocParamsPane";
describe("Excute Sproc Param Pane", () => {
const fakeExplorer = {} as Explorer;
const fakeSproc = {} as StoredProcedure;
const props = {
explorer: fakeExplorer,
storedProcedure: fakeSproc,
expandConsole: (): void => undefined,
closePanel: (): void => undefined,
};

View File

@@ -2,7 +2,6 @@ import { useBoolean } from "@uifabric/react-hooks";
import { IDropdownOption, IImageProps, Image, Stack, Text } from "office-ui-fabric-react";
import React, { FunctionComponent, useState } from "react";
import AddPropertyIcon from "../../../../images/Add-property.svg";
import Explorer from "../../Explorer";
import StoredProcedure from "../../Tree/StoredProcedure";
import {
GenericRightPaneComponent,
@@ -11,7 +10,7 @@ import {
import { InputParameter } from "./InputParameter";
interface ExecuteSprocParamsPaneProps {
explorer: Explorer;
expandConsole: () => void;
storedProcedure: StoredProcedure;
closePanel: () => void;
}
@@ -27,7 +26,7 @@ interface UnwrappedExecuteSprocParam {
}
export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPaneProps> = ({
explorer,
expandConsole,
storedProcedure,
closePanel,
}: ExecuteSprocParamsPaneProps): JSX.Element => {
@@ -43,7 +42,7 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
};
const genericPaneProps: GenericRightPaneProps = {
container: explorer,
expandConsole,
formError: formError,
formErrorDetail: formErrorsDetails,
id: "executesprocparamspane",

View File

@@ -3,11 +3,11 @@
exports[`Excute Sproc Param Pane should render Default properly 1`] = `
<ExecuteSprocParamsPane
closePanel={[Function]}
explorer={Object {}}
expandConsole={[Function]}
storedProcedure={Object {}}
>
<GenericRightPaneComponent
container={Object {}}
expandConsole={[Function]}
formError=""
formErrorDetail=""
id="executesprocparamspane"

View File

@@ -3,10 +3,9 @@ import React, { FunctionComponent, ReactNode } from "react";
import ErrorRedIcon from "../../../../images/error_red.svg";
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
import { KeyCodes } from "../../../Common/Constants";
import Explorer from "../../Explorer";
export interface GenericRightPaneProps {
container: Explorer;
expandConsole: () => void;
formError: string;
formErrorDetail: string;
id: string;
@@ -20,7 +19,7 @@ export interface GenericRightPaneProps {
}
export const GenericRightPaneComponent: FunctionComponent<GenericRightPaneProps> = ({
container,
expandConsole,
formError,
formErrorDetail,
id,
@@ -68,7 +67,7 @@ export const GenericRightPaneComponent: FunctionComponent<GenericRightPaneProps>
<span className="formErrors" title={formError}>
{formError}
</span>
<a className="errorLink" role="link" hidden={!formErrorDetail} onClick={showErrorDetail}>
<a className="errorLink" role="link" hidden={!formErrorDetail} onClick={expandConsole}>
More details
</a>
</span>
@@ -110,10 +109,6 @@ export const GenericRightPaneComponent: FunctionComponent<GenericRightPaneProps>
}
};
const showErrorDetail = (): void => {
container.expandConsole();
};
return (
<div tabIndex={-1} onKeyDown={onKeyDown}>
<div className="contextual-pane-out" onClick={onClose}></div>

View File

@@ -1,14 +0,0 @@
<div data-bind="visible: visible, event: { keydown: onPaneKeyDown }">
<div class="contextual-pane-out" data-bind="click: cancel, clickBubble: false"></div>
<div class="contextual-pane" id="gitHubReposPane">
<div class="contextual-pane-in">
<div class="paneContentContainer" data-bind="react: gitHubReposAdapter" />
</div>
<!-- Loader - Start -->
<div class="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" data-bind="visible: isExecuting">
<img class="dataExplorerLoader" alt="loading indicator image" src="/LoadingIndicator_3Squares.gif" />
</div>
<!-- Loader - End -->
</div>
</div>

View File

@@ -0,0 +1,19 @@
import { shallow } from "enzyme";
import React from "react";
import { GitHubClient } from "../../../GitHub/GitHubClient";
import { JunoClient } from "../../../Juno/JunoClient";
import Explorer from "../../Explorer";
import { GitHubReposPanel } from "./GitHubReposPanel";
const props = {
explorer: new Explorer(),
closePanel: (): void => undefined,
gitHubClientProp: new GitHubClient((): void => undefined),
junoClientProp: new JunoClient(),
openNotificationConsole: (): void => undefined,
};
describe("GitHub Repos Panel", () => {
it("should render Default properly", () => {
const wrapper = shallow(<GitHubReposPanel {...props} />);
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -1,27 +1,42 @@
import _ from "underscore";
import { Areas, HttpStatusCodes } from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import { GitHubClient, IGitHubPageInfo, IGitHubRepo } from "../../GitHub/GitHubClient";
import { IPinnedRepo, JunoClient } from "../../Juno/JunoClient";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import * as GitHubUtils from "../../Utils/GitHubUtils";
import * as JunoUtils from "../../Utils/JunoUtils";
import { AuthorizeAccessComponent } from "../Controls/GitHub/AuthorizeAccessComponent";
import { GitHubReposComponent, GitHubReposComponentProps, RepoListItem } from "../Controls/GitHub/GitHubReposComponent";
import { GitHubReposComponentAdapter } from "../Controls/GitHub/GitHubReposComponentAdapter";
import { BranchesProps, PinnedReposProps, UnpinnedReposProps } from "../Controls/GitHub/ReposListComponent";
import { ContextualPaneBase } from "./ContextualPaneBase";
import { handleError } from "../../Common/ErrorHandlingUtils";
import React from "react";
import { Areas, HttpStatusCodes } from "../../../Common/Constants";
import { handleError } from "../../../Common/ErrorHandlingUtils";
import { GitHubClient, IGitHubPageInfo, IGitHubRepo } from "../../../GitHub/GitHubClient";
import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import * as GitHubUtils from "../../../Utils/GitHubUtils";
import * as JunoUtils from "../../../Utils/JunoUtils";
import { AuthorizeAccessComponent } from "../../Controls/GitHub/AuthorizeAccessComponent";
import {
GitHubReposComponent,
GitHubReposComponentProps,
RepoListItem,
} from "../../Controls/GitHub/GitHubReposComponent";
import { ContentMainStyle } from "../../Controls/GitHub/GitHubStyleConstants";
import { BranchesProps, PinnedReposProps, UnpinnedReposProps } from "../../Controls/GitHub/ReposListComponent";
import Explorer from "../../Explorer";
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
import { PanelLoadingScreen } from "../PanelLoadingScreen";
interface GitHubReposPaneOptions extends ViewModels.PaneOptions {
gitHubClient: GitHubClient;
junoClient: JunoClient;
interface IGitHubReposPanelProps {
explorer: Explorer;
closePanel: () => void;
gitHubClientProp: GitHubClient;
junoClientProp: JunoClient;
openNotificationConsole: () => void;
}
export class GitHubReposPane extends ContextualPaneBase {
interface IGitHubReposPanelState {
showAuthorizationAcessState: boolean;
isExecuting: boolean;
errorMessage: string;
showErrorDetails: boolean;
gitHubReposState: GitHubReposComponentProps;
}
export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IGitHubReposPanelState> {
private static readonly PageSize = 30;
private isAddedRepo = false;
private gitHubClient: GitHubClient;
private junoClient: JunoClient;
@@ -29,73 +44,73 @@ export class GitHubReposPane extends ContextualPaneBase {
private pinnedReposProps: PinnedReposProps;
private unpinnedReposProps: UnpinnedReposProps;
private gitHubReposProps: GitHubReposComponentProps;
private gitHubReposAdapter: GitHubReposComponentAdapter;
private allGitHubRepos: IGitHubRepo[];
private allGitHubReposLastPageInfo?: IGitHubPageInfo;
private pinnedReposUpdated: boolean;
constructor(options: GitHubReposPaneOptions) {
super(options);
constructor(props: IGitHubReposPanelProps) {
super(props);
this.gitHubClient = options.gitHubClient;
this.junoClient = options.junoClient;
this.branchesProps = {};
this.pinnedReposProps = {
repos: [],
};
this.unpinnedReposProps = {
repos: [],
hasMore: true,
isLoading: true,
loadMore: (): Promise<void> => this.loadMoreUnpinnedRepos(),
};
this.gitHubReposProps = {
showAuthorizeAccess: true,
authorizeAccessProps: {
scope: this.getOAuthScope(),
authorizeAccess: (scope): void => this.connectToGitHub(scope),
},
reposListProps: {
branchesProps: this.branchesProps,
pinnedReposProps: this.pinnedReposProps,
unpinnedReposProps: this.unpinnedReposProps,
pinRepo: (item): Promise<void> => this.pinRepo(item),
unpinRepo: (item): Promise<void> => this.unpinRepo(item),
},
addRepoProps: {
container: this.container,
getRepo: (owner, repo): Promise<IGitHubRepo> => this.getRepo(owner, repo),
pinRepo: (item): Promise<void> => this.pinRepo(item),
},
resetConnection: (): void => this.setup(true),
onOkClick: (): Promise<void> => this.submit(),
onCancelClick: (): void => this.cancel(),
this.branchesProps = {};
this.pinnedReposProps = {
repos: [],
};
this.gitHubReposAdapter = new GitHubReposComponentAdapter(this.gitHubReposProps);
this.allGitHubRepos = [];
this.allGitHubReposLastPageInfo = undefined;
this.pinnedReposUpdated = false;
this.state = {
showAuthorizationAcessState: true,
isExecuting: false,
errorMessage: "",
showErrorDetails: false,
gitHubReposState: {
showAuthorizeAccess: !this.props.explorer.notebookManager?.gitHubOAuthService.isLoggedIn(),
authorizeAccessProps: {
scope: this.getOAuthScope(),
authorizeAccess: (scope): void => this.connectToGitHub(scope),
},
reposListProps: {
branchesProps: this.branchesProps,
pinnedReposProps: this.pinnedReposProps,
unpinnedReposProps: this.unpinnedReposProps,
pinRepo: (item): Promise<void> => this.pinRepo(item),
unpinRepo: (item): Promise<void> => this.unpinRepo(item),
},
addRepoProps: {
container: this.props.explorer,
getRepo: (owner, repo): Promise<IGitHubRepo> => this.getRepo(owner, repo),
pinRepo: (item): Promise<void> => this.pinRepo(item),
},
resetConnection: (): void => this.setup(true),
onOkClick: (): Promise<void> => this.submit(),
onCancelClick: (): void => this.props.closePanel(),
},
};
this.gitHubClient = this.props.gitHubClientProp;
this.junoClient = this.props.junoClientProp;
}
componentDidMount(): void {
this.open();
}
public open(): void {
this.resetData();
this.setup();
super.open();
}
public async submit(): Promise<void> {
const pinnedReposUpdated = this.pinnedReposUpdated;
const reposToPin: IPinnedRepo[] = this.pinnedReposProps.repos.map((repo) => JunoUtils.toPinnedRepo(repo));
// Submit resets data too
super.submit();
if (pinnedReposUpdated) {
try {
const response = await this.junoClient.updatePinnedRepos(reposToPin);
@@ -109,57 +124,44 @@ export class GitHubReposPane extends ContextualPaneBase {
}
public resetData(): void {
// Reset cached branches
this.branchesProps = {};
this.gitHubReposProps.reposListProps.branchesProps = this.branchesProps;
// Reset cached pinned and unpinned repos
this.pinnedReposProps.repos = [];
this.unpinnedReposProps.repos = [];
// Reset cached repos
this.allGitHubRepos = [];
this.allGitHubReposLastPageInfo = undefined;
// Reset flags
this.pinnedReposUpdated = false;
this.unpinnedReposProps.hasMore = true;
this.unpinnedReposProps.isLoading = true;
this.triggerRender();
super.resetData();
}
private getOAuthScope(): string {
return (
this.container.notebookManager?.gitHubOAuthService.getTokenObservable()()?.scope ||
this.props.explorer.notebookManager?.gitHubOAuthService.getTokenObservable()()?.scope ||
AuthorizeAccessComponent.Scopes.Public.key
);
}
private setup(forceShowConnectToGitHub = false): void {
forceShowConnectToGitHub || !this.container.notebookManager?.gitHubOAuthService.isLoggedIn()
forceShowConnectToGitHub || !this.props.explorer.notebookManager?.gitHubOAuthService.isLoggedIn()
? this.setupForConnectToGitHub()
: this.setupForManageRepos();
}
private setupForConnectToGitHub(): void {
this.gitHubReposProps.showAuthorizeAccess = true;
this.gitHubReposProps.authorizeAccessProps.scope = this.getOAuthScope();
this.isExecuting(false);
this.title(GitHubReposComponent.ConnectToGitHubTitle); // Used for telemetry
this.triggerRender();
this.setState({
isExecuting: false,
});
}
private async setupForManageRepos(): Promise<void> {
this.gitHubReposProps.showAuthorizeAccess = false;
this.isExecuting(false);
this.title(GitHubReposComponent.ManageGitHubRepoTitle); // Used for telemetry
this.setState({
isExecuting: false,
});
TelemetryProcessor.trace(Action.NotebooksGitHubManageRepo, ActionModifiers.Mark, {
dataExplorerArea: Areas.Notebook,
});
this.triggerRender();
this.refreshManageReposComponent();
}
@@ -182,15 +184,15 @@ export class GitHubReposPane extends ContextualPaneBase {
const branchesProps = this.branchesProps[GitHubUtils.toRepoFullName(repo.owner, repo.name)];
branchesProps.hasMore = true;
branchesProps.isLoading = true;
this.triggerRender();
try {
const response = await this.gitHubClient.getBranchesAsync(
repo.owner,
repo.name,
GitHubReposPane.PageSize,
GitHubReposPanel.PageSize,
branchesProps.lastPageInfo?.endCursor
);
if (response.status !== HttpStatusCodes.OK) {
throw new Error(`Received HTTP ${response.status} when fetching branches`);
}
@@ -205,19 +207,37 @@ export class GitHubReposPane extends ContextualPaneBase {
branchesProps.isLoading = false;
branchesProps.hasMore = branchesProps.lastPageInfo?.hasNextPage;
this.triggerRender();
this.setState({
gitHubReposState: {
...this.state.gitHubReposState,
reposListProps: {
...this.state.gitHubReposState.reposListProps,
branchesProps: {
...this.state.gitHubReposState.reposListProps.branchesProps,
[GitHubUtils.toRepoFullName(repo.owner, repo.name)]: branchesProps,
},
pinnedReposProps: {
repos: this.pinnedReposProps.repos,
},
unpinnedReposProps: {
...this.state.gitHubReposState.reposListProps.unpinnedReposProps,
repos: this.unpinnedReposProps.repos,
},
},
},
});
}
private async loadMoreUnpinnedRepos(): Promise<void> {
this.unpinnedReposProps.isLoading = true;
this.unpinnedReposProps.hasMore = true;
this.triggerRender();
try {
const response = await this.gitHubClient.getReposAsync(
GitHubReposPane.PageSize,
GitHubReposPanel.PageSize,
this.allGitHubReposLastPageInfo?.endCursor
);
if (response.status !== HttpStatusCodes.OK) {
throw new Error(`Received HTTP ${response.status} when fetching unpinned repos`);
}
@@ -233,7 +253,21 @@ export class GitHubReposPane extends ContextualPaneBase {
this.unpinnedReposProps.isLoading = false;
this.unpinnedReposProps.hasMore = this.allGitHubReposLastPageInfo?.hasNextPage;
this.triggerRender();
this.setState({
gitHubReposState: {
...this.state.gitHubReposState,
reposListProps: {
...this.state.gitHubReposState.reposListProps,
unpinnedReposProps: {
...this.state.gitHubReposState.reposListProps.unpinnedReposProps,
isLoading: this.unpinnedReposProps.isLoading,
hasMore: this.unpinnedReposProps.hasMore,
repos: this.unpinnedReposProps.repos,
},
},
},
});
}
private async getRepo(owner: string, repo: string): Promise<IGitHubRepo> {
@@ -242,7 +276,7 @@ export class GitHubReposPane extends ContextualPaneBase {
if (response.status !== HttpStatusCodes.OK) {
throw new Error(`Received HTTP ${response.status} when fetching repo`);
}
this.isAddedRepo = true;
return response.data;
} catch (error) {
handleError(error, "GitHubReposPane/getRepo", "Failed to fetch repo");
@@ -254,7 +288,7 @@ export class GitHubReposPane extends ContextualPaneBase {
this.pinnedReposUpdated = true;
const initialReposLength = this.pinnedReposProps.repos.length;
const existingRepo = _.find(this.pinnedReposProps.repos, (repo) => repo.key === item.key);
const existingRepo = this.pinnedReposProps.repos.find((repo) => repo.key === item.key);
if (existingRepo) {
existingRepo.branches = item.branches;
} else {
@@ -262,7 +296,6 @@ export class GitHubReposPane extends ContextualPaneBase {
}
this.unpinnedReposProps.repos = this.calculateUnpinnedRepos();
this.triggerRender();
if (this.pinnedReposProps.repos.length > initialReposLength) {
this.refreshBranchesForPinnedRepos();
@@ -273,7 +306,22 @@ export class GitHubReposPane extends ContextualPaneBase {
this.pinnedReposUpdated = true;
this.pinnedReposProps.repos = this.pinnedReposProps.repos.filter((pinnedRepo) => pinnedRepo.key !== item.key);
this.unpinnedReposProps.repos = this.calculateUnpinnedRepos();
this.triggerRender();
this.setState({
gitHubReposState: {
...this.state.gitHubReposState,
reposListProps: {
...this.state.gitHubReposState.reposListProps,
pinnedReposProps: {
repos: this.pinnedReposProps.repos,
},
unpinnedReposProps: {
...this.state.gitHubReposState.reposListProps.unpinnedReposProps,
repos: this.unpinnedReposProps.repos,
},
},
},
});
}
private async refreshManageReposComponent(): Promise<void> {
@@ -284,12 +332,12 @@ export class GitHubReposPane extends ContextualPaneBase {
private async refreshPinnedRepoListItems(): Promise<void> {
this.pinnedReposProps.repos = [];
this.triggerRender();
try {
const response = await this.junoClient.getPinnedRepos(
this.container.notebookManager?.gitHubOAuthService.getTokenObservable()()?.scope
this.props.explorer.notebookManager?.gitHubOAuthService.getTokenObservable()()?.scope
);
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
throw new Error(`Received HTTP ${response.status} when fetching pinned repos`);
}
@@ -305,7 +353,6 @@ export class GitHubReposPane extends ContextualPaneBase {
);
this.pinnedReposProps.repos = pinnedRepos;
this.triggerRender();
}
} catch (error) {
handleError(error, "GitHubReposPane/refreshPinnedReposListItems", "Failed to fetch pinned repos");
@@ -322,28 +369,85 @@ export class GitHubReposPane extends ContextualPaneBase {
isLoading: true,
loadMore: (): Promise<void> => this.loadMoreBranches(item.repo),
};
this.setState({
gitHubReposState: {
...this.state.gitHubReposState,
reposListProps: {
...this.state.gitHubReposState.reposListProps,
branchesProps: {
...this.state.gitHubReposState.reposListProps.branchesProps,
[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)]: this.branchesProps[item.key],
},
pinnedReposProps: {
repos: this.pinnedReposProps.repos,
},
unpinnedReposProps: {
...this.state.gitHubReposState.reposListProps.unpinnedReposProps,
repos: this.unpinnedReposProps.repos,
},
},
},
});
this.loadMoreBranches(item.repo);
} else {
if (this.isAddedRepo === false) {
this.setState({
gitHubReposState: {
...this.state.gitHubReposState,
reposListProps: {
...this.state.gitHubReposState.reposListProps,
pinnedReposProps: {
repos: this.pinnedReposProps.repos,
},
unpinnedReposProps: {
...this.state.gitHubReposState.reposListProps.unpinnedReposProps,
repos: this.unpinnedReposProps.repos,
},
},
},
});
}
}
});
this.isAddedRepo = false;
}
private async refreshUnpinnedRepoListItems(): Promise<void> {
this.allGitHubRepos = [];
this.allGitHubReposLastPageInfo = undefined;
this.unpinnedReposProps.repos = [];
this.loadMoreUnpinnedRepos();
}
private connectToGitHub(scope: string): void {
this.isExecuting(true);
this.setState({
isExecuting: true,
});
TelemetryProcessor.trace(Action.NotebooksGitHubAuthorize, ActionModifiers.Mark, {
dataExplorerArea: Areas.Notebook,
scopesSelected: scope,
});
this.container.notebookManager?.gitHubOAuthService.startOAuth(scope);
this.props.explorer.notebookManager?.gitHubOAuthService.startOAuth(scope);
}
private triggerRender(): void {
this.gitHubReposAdapter.triggerRender();
render(): JSX.Element {
return (
<form className="panelFormWrapper">
{this.state.errorMessage && (
<PanelInfoErrorComponent
message={this.state.errorMessage}
messageType="error"
showErrorDetails={this.state.showErrorDetails}
openNotificationConsole={this.props.openNotificationConsole}
/>
)}
<div className="panelMainContent" style={ContentMainStyle}>
<GitHubReposComponent {...this.state.gitHubReposState} />
</div>
{this.state.isExecuting && <PanelLoadingScreen />}
</form>
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -37,7 +37,7 @@ export const LoadQueryPane: FunctionComponent<LoadQueryPaneProps> = ({
const title = "Load Query";
const genericPaneProps: GenericRightPaneProps = {
container: explorer,
expandConsole: () => explorer.expandConsole(),
formError: formError,
formErrorDetail: formErrorsDetails,
id: "loadQueryPane",

View File

@@ -2,7 +2,7 @@
exports[`Load Query Pane should render Default properly 1`] = `
<GenericRightPaneComponent
container={Object {}}
expandConsole={[Function]}
formError=""
formErrorDetail=""
id="loadQueryPane"

View File

@@ -1,7 +1,6 @@
import AddCollectionPaneTemplate from "./AddCollectionPane.html";
import AddDatabasePaneTemplate from "./AddDatabasePane.html";
import CassandraAddCollectionPaneTemplate from "./CassandraAddCollectionPane.html";
import GitHubReposPaneTemplate from "./GitHubReposPane.html";
import GraphStylingPaneTemplate from "./GraphStylingPane.html";
import TableAddEntityPaneTemplate from "./Tables/TableAddEntityPane.html";
import TableEditEntityPaneTemplate from "./Tables/TableEditEntityPane.html";
@@ -63,12 +62,3 @@ export class CassandraAddCollectionPaneComponent {
};
}
}
export class GitHubReposPaneComponent {
constructor() {
return {
viewModel: PaneComponent,
template: GitHubReposPaneTemplate,
};
}
}

View File

@@ -11,11 +11,11 @@
margin: 20px 0;
overflow: auto;
& > * {
& > :not(.collapsibleSection) {
margin-bottom: @DefaultSpace;
& > * {
margin-bottom: @SmallSpace;
& > :not(:last-child) {
margin-bottom: @DefaultSpace;
}
}
@@ -23,7 +23,6 @@
font-size: @mediumFontSize;
width: @mediumFontSize;
margin: auto 0 auto @SmallSpace;
color: @InfoIconColor;
cursor: default;
vertical-align: middle;
}
@@ -49,10 +48,6 @@
font-size: @mediumFontSize;
padding: 0 @LargeSpace 0 @SmallSpace;
}
.collapsibleSection {
margin-bottom: 0;
}
}
}
@@ -99,7 +94,7 @@
}
.panelFooter {
padding: 20px 34px;
padding: 16px 34px;
border-top: solid 1px #bbbbbb;
& button {
@@ -123,8 +118,8 @@
cursor: pointer;
}
.panelGroupSpacing > * {
margin-bottom: @SmallSpace;
.panelGroupSpacing > :not(:last-child) {
margin-bottom: @DefaultSpace;
}
.fileUpload {
display: none !important;
@@ -170,3 +165,6 @@
.column-select-view {
margin: 20px 0px 0px 0px;
}
.panelSeparator::before {
background-color: #edebe9;
}

View File

@@ -31,7 +31,7 @@ export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProp
return (
formError && (
<Stack className="panelInfoErrorContainer" horizontal verticalAlign="start">
<Stack className="panelInfoErrorContainer" horizontal verticalAlign="center">
{icon}
<span className="panelWarningErrorDetailsLinkContainer">
<Text className="panelWarningErrorMessage" variant="small" data-testid="panelmessage">

View File

@@ -158,7 +158,6 @@ export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> =
};
const props: GenericRightPaneProps = {
container: container,
formError: formError,
formErrorDetail: formErrorDetail,
id: "publishnotebookpane",
@@ -167,6 +166,7 @@ export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> =
submitButtonText: "Publish",
onSubmit: () => submit(),
onClose: closePanel,
expandConsole: () => container.expandConsole(),
isSubmitButtonHidden: !isCodeOfConductAccepted,
};

View File

@@ -1,15 +1,15 @@
import { fireEvent, render, screen } from "@testing-library/react";
import { mount, ReactWrapper } from "enzyme";
import React from "react";
import Explorer from "../../Explorer";
import { RightPaneForm } from "./RightPaneForm";
const onClose = jest.fn();
const onSubmit = jest.fn();
const expandConsole = jest.fn();
const props = {
closePanel: (): void => undefined,
container: new Explorer(),
expandConsole,
formError: "",
formErrorDetail: "",
id: "loadQueryPane",

View File

@@ -1,13 +1,12 @@
import { IconButton } from "office-ui-fabric-react/lib/Button";
import React, { FunctionComponent, ReactNode } from "react";
import { KeyCodes } from "../../../Common/Constants";
import Explorer from "../../Explorer";
import { PanelFooterComponent } from "../PanelFooterComponent";
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "../PanelInfoErrorComponent";
import { PanelLoadingScreen } from "../PanelLoadingScreen";
export interface RightPaneFormProps {
container: Explorer;
expandConsole: () => void;
formError: string;
formErrorDetail: string;
id: string;
@@ -21,7 +20,7 @@ export interface RightPaneFormProps {
}
export const RightPaneForm: FunctionComponent<RightPaneFormProps> = ({
container,
expandConsole,
formError,
formErrorDetail,
id,
@@ -70,16 +69,12 @@ export const RightPaneForm: FunctionComponent<RightPaneFormProps> = ({
}
};
const showErrorDetail = (): void => {
container.expandConsole();
};
const panelInfoErrorProps: PanelInfoErrorProps = {
messageType: "error",
message: formError,
formError: formError !== "",
showErrorDetails: formErrorDetail !== "",
openNotificationConsole: showErrorDetail,
openNotificationConsole: expandConsole,
};
return (

View File

@@ -3,504 +3,7 @@
exports[`Load Query Pane should render Default properly 1`] = `
<RightPaneForm
closePanel={[Function]}
container={
Explorer {
"_closeModalDialog": [Function],
"_closeSynapseLinkModalDialog": [Function],
"_isAfecFeatureRegistered": [Function],
"_isInitializingNotebooks": false,
"_panes": Array [
AddDatabasePane {
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"container": [Circular],
"costsVisible": [Function],
"databaseCreateNewShared": [Function],
"databaseId": [Function],
"databaseIdLabel": [Function],
"databaseIdPlaceHolder": [Function],
"databaseIdTooltipText": [Function],
"databaseLevelThroughputTooltipText": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "adddatabasepane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFreeTierAccount": [Function],
"isTemplateReady": [Function],
"maxAutoPilotThroughputSet": [Function],
"maxThroughputRU": [Function],
"maxThroughputRUText": [Function],
"minThroughputRU": [Function],
"onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function],
"ruToolTipText": [Function],
"showUpsellMessage": [Function],
"throughput": [Function],
"throughputRangeText": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"upsellAnchorText": [Function],
"upsellAnchorUrl": [Function],
"upsellMessage": [Function],
"upsellMessageAriaLabel": [Function],
"visible": [Function],
},
AddCollectionPane {
"_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function],
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"collectionId": [Function],
"collectionIdTitle": [Function],
"collectionWithThroughputInShared": [Function],
"collectionWithThroughputInSharedTitle": [Function],
"container": [Circular],
"costsVisible": [Function],
"databaseCreateNew": [Function],
"databaseCreateNewShared": [Function],
"databaseHasSharedOffer": [Function],
"databaseId": [Function],
"databaseIds": [Function],
"dedicatedRequestUnitsUsageCost": [Function],
"displayCollectionThroughput": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"formWarnings": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "addcollectionpane",
"isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFixedStorageSelected": [Function],
"isFreeTierAccount": [Function],
"isNonTableApi": [Function],
"isPreferredApiTable": [Function],
"isSharedAutoPilotSelected": [Function],
"isSynapseLinkSupported": [Function],
"isSynapseLinkUpdating": [Function],
"isTemplateReady": [Function],
"isTryCosmosDBSubscription": [Function],
"isUnlimitedStorageSelected": [Function],
"largePartitionKey": [Function],
"lowerCasePartitionKeyName": [Function],
"maxCollectionsReached": [Function],
"maxCollectionsReachedMessage": [Function],
"maxThroughputRU": [Function],
"minThroughputRU": [Function],
"onMoreDetailsKeyPress": [Function],
"partitionKey": [Function],
"partitionKeyName": [Function],
"partitionKeyPattern": [Function],
"partitionKeyPlaceholder": [Function],
"partitionKeyTitle": [Function],
"partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function],
"ruToolTipText": [Function],
"sharedAutoPilotThroughput": [Function],
"sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function],
"showAnalyticalStore": [Function],
"showEnableSynapseLink": [Function],
"showIndexingOptionsForSharedThroughput": [Function],
"showUpsellMessage": [Function],
"storage": [Function],
"throughputDatabase": [Function],
"throughputMultiPartition": [Function],
"throughputRangeText": [Function],
"throughputSinglePartition": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"ttl90DaysEnabled": [Function],
"uniqueKeys": [Function],
"uniqueKeysPlaceholder": [Function],
"uniqueKeysVisible": [Function],
"upsellAnchorText": [Function],
"upsellAnchorUrl": [Function],
"upsellMessage": [Function],
"upsellMessageAriaLabel": [Function],
"useIndexingForSharedThroughput": [Function],
"visible": [Function],
},
GraphStylingPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"graphConfigUIData": Object {
"nodeCaptionChoice": [Function],
"nodeColorKeyChoice": [Function],
"nodeIconChoice": [Function],
"nodeIconSet": [Function],
"nodeProperties": [Function],
"nodePropertiesWithNone": [Function],
"showNeighborType": [Function],
},
"id": "graphstylingpane",
"isExecuting": [Function],
"isTemplateReady": [Function],
"title": [Function],
"visible": [Function],
},
CassandraAddCollectionPane {
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"container": [Circular],
"costsVisible": [Function],
"createTableQuery": [Function],
"dedicateTableThroughput": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFreeTierAccount": [Function],
"isSharedAutoPilotSelected": [Function],
"isTemplateReady": [Function],
"keyspaceCreateNew": [Function],
"keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function],
"keyspaceIds": [Function],
"keyspaceOffers": Map {},
"keyspaceThroughput": [Function],
"maxThroughputRU": [Function],
"minThroughputRU": [Function],
"requestUnitsUsageCostDedicated": [Function],
"requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function],
"sharedAutoPilotThroughput": [Function],
"sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function],
"sharedThroughputSpendAckVisible": [Function],
"tableId": [Function],
"throughput": [Function],
"throughputRangeText": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"userTableQuery": [Function],
"visible": [Function],
},
],
"_refreshSparkEnabledStateForAccount": [Function],
"_resetNotebookWorkspace": [Function],
"addCollectionPane": AddCollectionPane {
"_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function],
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"collectionId": [Function],
"collectionIdTitle": [Function],
"collectionWithThroughputInShared": [Function],
"collectionWithThroughputInSharedTitle": [Function],
"container": [Circular],
"costsVisible": [Function],
"databaseCreateNew": [Function],
"databaseCreateNewShared": [Function],
"databaseHasSharedOffer": [Function],
"databaseId": [Function],
"databaseIds": [Function],
"dedicatedRequestUnitsUsageCost": [Function],
"displayCollectionThroughput": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"formWarnings": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "addcollectionpane",
"isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFixedStorageSelected": [Function],
"isFreeTierAccount": [Function],
"isNonTableApi": [Function],
"isPreferredApiTable": [Function],
"isSharedAutoPilotSelected": [Function],
"isSynapseLinkSupported": [Function],
"isSynapseLinkUpdating": [Function],
"isTemplateReady": [Function],
"isTryCosmosDBSubscription": [Function],
"isUnlimitedStorageSelected": [Function],
"largePartitionKey": [Function],
"lowerCasePartitionKeyName": [Function],
"maxCollectionsReached": [Function],
"maxCollectionsReachedMessage": [Function],
"maxThroughputRU": [Function],
"minThroughputRU": [Function],
"onMoreDetailsKeyPress": [Function],
"partitionKey": [Function],
"partitionKeyName": [Function],
"partitionKeyPattern": [Function],
"partitionKeyPlaceholder": [Function],
"partitionKeyTitle": [Function],
"partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function],
"ruToolTipText": [Function],
"sharedAutoPilotThroughput": [Function],
"sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function],
"showAnalyticalStore": [Function],
"showEnableSynapseLink": [Function],
"showIndexingOptionsForSharedThroughput": [Function],
"showUpsellMessage": [Function],
"storage": [Function],
"throughputDatabase": [Function],
"throughputMultiPartition": [Function],
"throughputRangeText": [Function],
"throughputSinglePartition": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"ttl90DaysEnabled": [Function],
"uniqueKeys": [Function],
"uniqueKeysPlaceholder": [Function],
"uniqueKeysVisible": [Function],
"upsellAnchorText": [Function],
"upsellAnchorUrl": [Function],
"upsellMessage": [Function],
"upsellMessageAriaLabel": [Function],
"useIndexingForSharedThroughput": [Function],
"visible": [Function],
},
"addCollectionText": [Function],
"addDatabasePane": AddDatabasePane {
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"container": [Circular],
"costsVisible": [Function],
"databaseCreateNewShared": [Function],
"databaseId": [Function],
"databaseIdLabel": [Function],
"databaseIdPlaceHolder": [Function],
"databaseIdTooltipText": [Function],
"databaseLevelThroughputTooltipText": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "adddatabasepane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFreeTierAccount": [Function],
"isTemplateReady": [Function],
"maxAutoPilotThroughputSet": [Function],
"maxThroughputRU": [Function],
"maxThroughputRUText": [Function],
"minThroughputRU": [Function],
"onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function],
"ruToolTipText": [Function],
"showUpsellMessage": [Function],
"throughput": [Function],
"throughputRangeText": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"upsellAnchorText": [Function],
"upsellAnchorUrl": [Function],
"upsellMessage": [Function],
"upsellMessageAriaLabel": [Function],
"visible": [Function],
},
"addDatabaseText": [Function],
"arcadiaToken": [Function],
"canExceedMaximumValue": [Function],
"canSaveQueries": [Function],
"cassandraAddCollectionPane": CassandraAddCollectionPane {
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"container": [Circular],
"costsVisible": [Function],
"createTableQuery": [Function],
"dedicateTableThroughput": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFreeTierAccount": [Function],
"isSharedAutoPilotSelected": [Function],
"isTemplateReady": [Function],
"keyspaceCreateNew": [Function],
"keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function],
"keyspaceIds": [Function],
"keyspaceOffers": Map {},
"keyspaceThroughput": [Function],
"maxThroughputRU": [Function],
"minThroughputRU": [Function],
"requestUnitsUsageCostDedicated": [Function],
"requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function],
"sharedAutoPilotThroughput": [Function],
"sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function],
"sharedThroughputSpendAckVisible": [Function],
"tableId": [Function],
"throughput": [Function],
"throughputRangeText": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"userTableQuery": [Function],
"visible": [Function],
},
"clickHostedAccountSwitch": [Function],
"clickHostedDirectorySwitch": [Function],
"closeDialog": undefined,
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"collectionCreationDefaults": Object {
"storage": "100",
"throughput": Object {
"fixed": 400,
"shared": 400,
"unlimited": 400,
"unlimitedmax": 1000000,
"unlimitedmin": 400,
},
},
"collectionTitle": [Function],
"collectionTreeNodeAltText": [Function],
"commandBarComponentAdapter": CommandBarComponentAdapter {
"container": [Circular],
"isNotebookTabActive": [Function],
"parameters": [Function],
"tabsButtons": Array [],
},
"databaseAccount": [Function],
"databases": [Function],
"defaultExperience": [Function],
"deleteCollectionText": [Function],
"deleteDatabaseText": [Function],
"graphStylingPane": GraphStylingPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"graphConfigUIData": Object {
"nodeCaptionChoice": [Function],
"nodeColorKeyChoice": [Function],
"nodeIconChoice": [Function],
"nodeIconSet": [Function],
"nodeProperties": [Function],
"nodePropertiesWithNone": [Function],
"showNeighborType": [Function],
},
"id": "graphstylingpane",
"isExecuting": [Function],
"isTemplateReady": [Function],
"title": [Function],
"visible": [Function],
},
"hasStorageAnalyticsAfecFeature": [Function],
"isAccountReady": [Function],
"isAutoscaleDefaultEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isGitHubPaneEnabled": [Function],
"isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function],
"isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
"isPublishNotebookPaneEnabled": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function],
"isSchemaEnabled": [Function],
"isServerlessEnabled": [Function],
"isSparkEnabled": [Function],
"isSparkEnabledForAccount": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
"memoryUsageInfo": [Function],
"notebookBasePath": [Function],
"notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function],
"openDialog": undefined,
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
"container": [Circular],
},
"refreshDatabaseAccount": [Function],
"refreshNotebookList": [Function],
"refreshTreeTitle": [Function],
"resourceTokenCollection": [Function],
"resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function],
"resourceTokenPartitionKey": [Function],
"resourceTree": ResourceTreeAdapter {
"container": [Circular],
"copyNotebook": [Function],
"databaseCollectionIdMap": Map {},
"koSubsCollectionIdMap": Map {},
"koSubsDatabaseIdMap": Map {},
"parameters": [Function],
},
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
"container": [Circular],
"parameters": [Function],
},
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
"setNotificationConsoleData": undefined,
"signInAad": [Function],
"sparkClusterConnectionInfo": [Function],
"splitter": Splitter {
"bounds": Object {
"max": 400,
"min": 240,
},
"direction": "vertical",
"isCollapsed": [Function],
"leftSideId": "resourcetree",
"onResizeStart": [Function],
"onResizeStop": [Function],
"splitterId": "h_splitter1",
},
"tabsManager": TabsManager {
"activeTab": [Function],
"openedTabs": [Function],
},
"toggleLeftPaneExpandedKeyPress": [Function],
}
}
expandConsole={[MockFunction]}
formError=""
formErrorDetail=""
id="loadQueryPane"
@@ -1554,7 +1057,7 @@ exports[`Load Query Pane should render Default properly 1`] = `
>
<button
aria-label="Close pane"
className="ms-Button ms-Button--icon closePaneBtn root-104"
className="ms-Button ms-Button--icon closePaneBtn root-40"
data-is-focusable={true}
data-testid="closePaneBtn"
onClick={[Function]}
@@ -1568,16 +1071,16 @@ exports[`Load Query Pane should render Default properly 1`] = `
type="button"
>
<span
className="ms-Button-flexContainer flexContainer-105"
className="ms-Button-flexContainer flexContainer-41"
data-automationid="splitbuttonprimary"
>
<Component
className="ms-Button-icon icon-107"
className="ms-Button-icon icon-43"
iconName="Cancel"
>
<i
aria-hidden={true}
className="ms-Icon root-37 css-112 ms-Button-icon icon-107"
className="ms-Icon root-37 css-48 ms-Button-icon icon-43"
data-icon-name="Cancel"
role="presentation"
style={
@@ -1600,7 +1103,7 @@ exports[`Load Query Pane should render Default properly 1`] = `
formError={false}
message=""
messageType="error"
openNotificationConsole={[Function]}
openNotificationConsole={[MockFunction]}
showErrorDetails={false}
/>
<form
@@ -3294,7 +2797,7 @@ exports[`Load Query Pane should render Default properly 1`] = `
>
<button
aria-label="Load"
className="ms-Button ms-Button--primary root-113"
className="ms-Button ms-Button--primary root-49"
data-is-focusable={true}
data-testid="submit"
id="sidePanelOkButton"
@@ -3307,14 +2810,14 @@ exports[`Load Query Pane should render Default properly 1`] = `
type="submit"
>
<span
className="ms-Button-flexContainer flexContainer-105"
className="ms-Button-flexContainer flexContainer-41"
data-automationid="splitbuttonprimary"
>
<span
className="ms-Button-textContainer textContainer-106"
className="ms-Button-textContainer textContainer-42"
>
<span
className="ms-Button-label label-114"
className="ms-Button-label label-50"
id="id__3"
key="id__3"
>

View File

@@ -32,7 +32,7 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({
const title = "Save Query";
const { canSaveQueries } = explorer;
const genericPaneProps: GenericRightPaneProps = {
container: explorer,
expandConsole: () => explorer.expandConsole(),
formError: formError,
formErrorDetail: formErrorsDetails,
id: "saveQueryPane",

View File

@@ -2,11 +2,7 @@
exports[`Save Query Pane should render Default properly 1`] = `
<GenericRightPaneComponent
container={
Object {
"canSaveQueries": [Function],
}
}
expandConsole={[Function]}
formError=""
formErrorDetail=""
id="saveQueryPane"

View File

@@ -2,10 +2,9 @@ import { shallow } from "enzyme";
import React from "react";
import { DatabaseAccount } from "../../../Contracts/DataModels";
import { updateUserContext } from "../../../UserContext";
import Explorer from "../../Explorer";
import { SettingsPane } from "./SettingsPane";
const props = {
explorer: new Explorer(),
expandConsole: (): void => undefined,
closePanel: (): void => undefined,
};
describe("Settings Pane", () => {

View File

@@ -7,16 +7,15 @@ import { LocalStorageUtility, StorageKey } from "../../../Shared/StorageUtility"
import * as StringUtility from "../../../Shared/StringUtility";
import { userContext } from "../../../UserContext";
import { logConsoleInfo } from "../../../Utils/NotificationConsoleUtils";
import Explorer from "../../Explorer";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
export interface SettingsPaneProps {
explorer: Explorer;
expandConsole: () => void;
closePanel: () => void;
}
export const SettingsPane: FunctionComponent<SettingsPaneProps> = ({
explorer: container,
expandConsole,
closePanel,
}: SettingsPaneProps) => {
const [formErrors, setFormErrors] = useState<string>("");
@@ -104,7 +103,7 @@ export const SettingsPane: FunctionComponent<SettingsPaneProps> = ({
};
const genericPaneProps: RightPaneFormProps = {
container,
expandConsole,
formError: formErrors,
formErrorDetail: "",
id: "settingspane",

View File

@@ -92,7 +92,6 @@ export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
}
};
const genericPaneProps: GenericRightPaneProps = {
container: container,
formError: formErrors,
formErrorDetail: formErrorsDetails,
id: "stringInputPane",
@@ -101,6 +100,7 @@ export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
submitButtonText: submitButtonLabel,
onClose: closePanel,
onSubmit: submit,
expandConsole: () => container.expandConsole(),
};
return (
<GenericRightPaneComponent {...genericPaneProps}>

View File

@@ -328,7 +328,7 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
selectedKey={entity.type}
entityPropertyPlaceHolder={detailedHelp}
entityValuePlaceholder={entity.entityValuePlaceholder}
entityValue={entity.value}
entityValue={entity.value?.toString()}
isEntityTypeDate={entity.isEntityTypeDate}
entityTimeValue={entity.entityTimeValue}
isEntityValueDisable={entity.isEntityValueDisable}

View File

@@ -11,7 +11,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
}
>
<GenericRightPaneComponent
container={Object {}}
expandConsole={[Function]}
formError=""
formErrorDetail=""
id="querySelectPane"

View File

@@ -32,7 +32,6 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
const [isAvailableColumnChecked, setIsAvailableColumnChecked] = useState<boolean>(true);
const genericPaneProps: GenericRightPaneProps = {
container: explorer,
formError: "",
formErrorDetail: "",
id: "querySelectPane",
@@ -41,6 +40,7 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
submitButtonText: "OK",
onClose: () => closePanel(),
onSubmit: () => submit(),
expandConsole: () => explorer.expandConsole(),
};
const submit = (): void => {

View File

@@ -1,18 +1,17 @@
import React, { ChangeEvent, FunctionComponent, useState } from "react";
import { Upload } from "../../../Common/Upload/Upload";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
import Explorer from "../../Explorer";
import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
export interface UploadFilePanelProps {
explorer: Explorer;
expandConsole: () => void;
closePanel: () => void;
uploadFile: (name: string, content: string) => Promise<NotebookContentItem>;
}
export const UploadFilePane: FunctionComponent<UploadFilePanelProps> = ({
explorer: container,
expandConsole,
closePanel,
uploadFile,
}: UploadFilePanelProps) => {
@@ -90,7 +89,7 @@ export const UploadFilePane: FunctionComponent<UploadFilePanelProps> = ({
};
const genericPaneProps: RightPaneFormProps = {
container: container,
expandConsole,
formError: formErrors,
formErrorDetail: formErrorsDetails,
id: "uploadFilePane",

View File

@@ -68,7 +68,7 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({
};
const genericPaneProps: RightPaneFormProps = {
container: explorer,
expandConsole: () => explorer.expandConsole(),
formError,
formErrorDetail,
id: "uploaditemspane",

View File

@@ -2,504 +2,7 @@
exports[`Upload Items Pane should render Default properly 1`] = `
<RightPaneForm
container={
Explorer {
"_closeModalDialog": [Function],
"_closeSynapseLinkModalDialog": [Function],
"_isAfecFeatureRegistered": [Function],
"_isInitializingNotebooks": false,
"_panes": Array [
AddDatabasePane {
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"container": [Circular],
"costsVisible": [Function],
"databaseCreateNewShared": [Function],
"databaseId": [Function],
"databaseIdLabel": [Function],
"databaseIdPlaceHolder": [Function],
"databaseIdTooltipText": [Function],
"databaseLevelThroughputTooltipText": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "adddatabasepane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFreeTierAccount": [Function],
"isTemplateReady": [Function],
"maxAutoPilotThroughputSet": [Function],
"maxThroughputRU": [Function],
"maxThroughputRUText": [Function],
"minThroughputRU": [Function],
"onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function],
"ruToolTipText": [Function],
"showUpsellMessage": [Function],
"throughput": [Function],
"throughputRangeText": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"upsellAnchorText": [Function],
"upsellAnchorUrl": [Function],
"upsellMessage": [Function],
"upsellMessageAriaLabel": [Function],
"visible": [Function],
},
AddCollectionPane {
"_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function],
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"collectionId": [Function],
"collectionIdTitle": [Function],
"collectionWithThroughputInShared": [Function],
"collectionWithThroughputInSharedTitle": [Function],
"container": [Circular],
"costsVisible": [Function],
"databaseCreateNew": [Function],
"databaseCreateNewShared": [Function],
"databaseHasSharedOffer": [Function],
"databaseId": [Function],
"databaseIds": [Function],
"dedicatedRequestUnitsUsageCost": [Function],
"displayCollectionThroughput": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"formWarnings": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "addcollectionpane",
"isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFixedStorageSelected": [Function],
"isFreeTierAccount": [Function],
"isNonTableApi": [Function],
"isPreferredApiTable": [Function],
"isSharedAutoPilotSelected": [Function],
"isSynapseLinkSupported": [Function],
"isSynapseLinkUpdating": [Function],
"isTemplateReady": [Function],
"isTryCosmosDBSubscription": [Function],
"isUnlimitedStorageSelected": [Function],
"largePartitionKey": [Function],
"lowerCasePartitionKeyName": [Function],
"maxCollectionsReached": [Function],
"maxCollectionsReachedMessage": [Function],
"maxThroughputRU": [Function],
"minThroughputRU": [Function],
"onMoreDetailsKeyPress": [Function],
"partitionKey": [Function],
"partitionKeyName": [Function],
"partitionKeyPattern": [Function],
"partitionKeyPlaceholder": [Function],
"partitionKeyTitle": [Function],
"partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function],
"ruToolTipText": [Function],
"sharedAutoPilotThroughput": [Function],
"sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function],
"showAnalyticalStore": [Function],
"showEnableSynapseLink": [Function],
"showIndexingOptionsForSharedThroughput": [Function],
"showUpsellMessage": [Function],
"storage": [Function],
"throughputDatabase": [Function],
"throughputMultiPartition": [Function],
"throughputRangeText": [Function],
"throughputSinglePartition": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"ttl90DaysEnabled": [Function],
"uniqueKeys": [Function],
"uniqueKeysPlaceholder": [Function],
"uniqueKeysVisible": [Function],
"upsellAnchorText": [Function],
"upsellAnchorUrl": [Function],
"upsellMessage": [Function],
"upsellMessageAriaLabel": [Function],
"useIndexingForSharedThroughput": [Function],
"visible": [Function],
},
GraphStylingPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"graphConfigUIData": Object {
"nodeCaptionChoice": [Function],
"nodeColorKeyChoice": [Function],
"nodeIconChoice": [Function],
"nodeIconSet": [Function],
"nodeProperties": [Function],
"nodePropertiesWithNone": [Function],
"showNeighborType": [Function],
},
"id": "graphstylingpane",
"isExecuting": [Function],
"isTemplateReady": [Function],
"title": [Function],
"visible": [Function],
},
CassandraAddCollectionPane {
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"container": [Circular],
"costsVisible": [Function],
"createTableQuery": [Function],
"dedicateTableThroughput": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFreeTierAccount": [Function],
"isSharedAutoPilotSelected": [Function],
"isTemplateReady": [Function],
"keyspaceCreateNew": [Function],
"keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function],
"keyspaceIds": [Function],
"keyspaceOffers": Map {},
"keyspaceThroughput": [Function],
"maxThroughputRU": [Function],
"minThroughputRU": [Function],
"requestUnitsUsageCostDedicated": [Function],
"requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function],
"sharedAutoPilotThroughput": [Function],
"sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function],
"sharedThroughputSpendAckVisible": [Function],
"tableId": [Function],
"throughput": [Function],
"throughputRangeText": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"userTableQuery": [Function],
"visible": [Function],
},
],
"_refreshSparkEnabledStateForAccount": [Function],
"_resetNotebookWorkspace": [Function],
"addCollectionPane": AddCollectionPane {
"_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function],
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"collectionId": [Function],
"collectionIdTitle": [Function],
"collectionWithThroughputInShared": [Function],
"collectionWithThroughputInSharedTitle": [Function],
"container": [Circular],
"costsVisible": [Function],
"databaseCreateNew": [Function],
"databaseCreateNewShared": [Function],
"databaseHasSharedOffer": [Function],
"databaseId": [Function],
"databaseIds": [Function],
"dedicatedRequestUnitsUsageCost": [Function],
"displayCollectionThroughput": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"formWarnings": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "addcollectionpane",
"isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFixedStorageSelected": [Function],
"isFreeTierAccount": [Function],
"isNonTableApi": [Function],
"isPreferredApiTable": [Function],
"isSharedAutoPilotSelected": [Function],
"isSynapseLinkSupported": [Function],
"isSynapseLinkUpdating": [Function],
"isTemplateReady": [Function],
"isTryCosmosDBSubscription": [Function],
"isUnlimitedStorageSelected": [Function],
"largePartitionKey": [Function],
"lowerCasePartitionKeyName": [Function],
"maxCollectionsReached": [Function],
"maxCollectionsReachedMessage": [Function],
"maxThroughputRU": [Function],
"minThroughputRU": [Function],
"onMoreDetailsKeyPress": [Function],
"partitionKey": [Function],
"partitionKeyName": [Function],
"partitionKeyPattern": [Function],
"partitionKeyPlaceholder": [Function],
"partitionKeyTitle": [Function],
"partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function],
"ruToolTipText": [Function],
"sharedAutoPilotThroughput": [Function],
"sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function],
"showAnalyticalStore": [Function],
"showEnableSynapseLink": [Function],
"showIndexingOptionsForSharedThroughput": [Function],
"showUpsellMessage": [Function],
"storage": [Function],
"throughputDatabase": [Function],
"throughputMultiPartition": [Function],
"throughputRangeText": [Function],
"throughputSinglePartition": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"ttl90DaysEnabled": [Function],
"uniqueKeys": [Function],
"uniqueKeysPlaceholder": [Function],
"uniqueKeysVisible": [Function],
"upsellAnchorText": [Function],
"upsellAnchorUrl": [Function],
"upsellMessage": [Function],
"upsellMessageAriaLabel": [Function],
"useIndexingForSharedThroughput": [Function],
"visible": [Function],
},
"addCollectionText": [Function],
"addDatabasePane": AddDatabasePane {
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"container": [Circular],
"costsVisible": [Function],
"databaseCreateNewShared": [Function],
"databaseId": [Function],
"databaseIdLabel": [Function],
"databaseIdPlaceHolder": [Function],
"databaseIdTooltipText": [Function],
"databaseLevelThroughputTooltipText": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "adddatabasepane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFreeTierAccount": [Function],
"isTemplateReady": [Function],
"maxAutoPilotThroughputSet": [Function],
"maxThroughputRU": [Function],
"maxThroughputRUText": [Function],
"minThroughputRU": [Function],
"onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function],
"ruToolTipText": [Function],
"showUpsellMessage": [Function],
"throughput": [Function],
"throughputRangeText": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"upsellAnchorText": [Function],
"upsellAnchorUrl": [Function],
"upsellMessage": [Function],
"upsellMessageAriaLabel": [Function],
"visible": [Function],
},
"addDatabaseText": [Function],
"arcadiaToken": [Function],
"canExceedMaximumValue": [Function],
"canSaveQueries": [Function],
"cassandraAddCollectionPane": CassandraAddCollectionPane {
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"container": [Circular],
"costsVisible": [Function],
"createTableQuery": [Function],
"dedicateTableThroughput": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFreeTierAccount": [Function],
"isSharedAutoPilotSelected": [Function],
"isTemplateReady": [Function],
"keyspaceCreateNew": [Function],
"keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function],
"keyspaceIds": [Function],
"keyspaceOffers": Map {},
"keyspaceThroughput": [Function],
"maxThroughputRU": [Function],
"minThroughputRU": [Function],
"requestUnitsUsageCostDedicated": [Function],
"requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function],
"sharedAutoPilotThroughput": [Function],
"sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function],
"sharedThroughputSpendAckVisible": [Function],
"tableId": [Function],
"throughput": [Function],
"throughputRangeText": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"userTableQuery": [Function],
"visible": [Function],
},
"clickHostedAccountSwitch": [Function],
"clickHostedDirectorySwitch": [Function],
"closeDialog": undefined,
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"collectionCreationDefaults": Object {
"storage": "100",
"throughput": Object {
"fixed": 400,
"shared": 400,
"unlimited": 400,
"unlimitedmax": 1000000,
"unlimitedmin": 400,
},
},
"collectionTitle": [Function],
"collectionTreeNodeAltText": [Function],
"commandBarComponentAdapter": CommandBarComponentAdapter {
"container": [Circular],
"isNotebookTabActive": [Function],
"parameters": [Function],
"tabsButtons": Array [],
},
"databaseAccount": [Function],
"databases": [Function],
"defaultExperience": [Function],
"deleteCollectionText": [Function],
"deleteDatabaseText": [Function],
"graphStylingPane": GraphStylingPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"graphConfigUIData": Object {
"nodeCaptionChoice": [Function],
"nodeColorKeyChoice": [Function],
"nodeIconChoice": [Function],
"nodeIconSet": [Function],
"nodeProperties": [Function],
"nodePropertiesWithNone": [Function],
"showNeighborType": [Function],
},
"id": "graphstylingpane",
"isExecuting": [Function],
"isTemplateReady": [Function],
"title": [Function],
"visible": [Function],
},
"hasStorageAnalyticsAfecFeature": [Function],
"isAccountReady": [Function],
"isAutoscaleDefaultEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isGitHubPaneEnabled": [Function],
"isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function],
"isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
"isPublishNotebookPaneEnabled": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function],
"isSchemaEnabled": [Function],
"isServerlessEnabled": [Function],
"isSparkEnabled": [Function],
"isSparkEnabledForAccount": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
"memoryUsageInfo": [Function],
"notebookBasePath": [Function],
"notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function],
"openDialog": undefined,
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
"container": [Circular],
},
"refreshDatabaseAccount": [Function],
"refreshNotebookList": [Function],
"refreshTreeTitle": [Function],
"resourceTokenCollection": [Function],
"resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function],
"resourceTokenPartitionKey": [Function],
"resourceTree": ResourceTreeAdapter {
"container": [Circular],
"copyNotebook": [Function],
"databaseCollectionIdMap": Map {},
"koSubsCollectionIdMap": Map {},
"koSubsDatabaseIdMap": Map {},
"parameters": [Function],
},
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
"container": [Circular],
"parameters": [Function],
},
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
"setNotificationConsoleData": undefined,
"signInAad": [Function],
"sparkClusterConnectionInfo": [Function],
"splitter": Splitter {
"bounds": Object {
"max": 400,
"min": 240,
},
"direction": "vertical",
"isCollapsed": [Function],
"leftSideId": "resourcetree",
"onResizeStart": [Function],
"onResizeStop": [Function],
"splitterId": "h_splitter1",
},
"tabsManager": TabsManager {
"activeTab": [Function],
"openedTabs": [Function],
},
"toggleLeftPaneExpandedKeyPress": [Function],
}
}
expandConsole={[Function]}
formError=""
formErrorDetail=""
id="uploaditemspane"

View File

@@ -404,6 +404,773 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
"defaultExperience": [Function],
"deleteCollectionText": [Function],
"deleteDatabaseText": [Function],
"gitHubClient": GitHubClient {
"errorCallback": [Function],
"ocktokit": OctokitWithDefaults {
"actions": Object {
"addSelectedRepoToOrgSecret": [Function],
"cancelWorkflowRun": [Function],
"createOrUpdateOrgSecret": [Function],
"createOrUpdateRepoSecret": [Function],
"createOrUpdateSecretForRepo": [Function],
"createRegistrationToken": [Function],
"createRegistrationTokenForOrg": [Function],
"createRegistrationTokenForRepo": [Function],
"createRemoveToken": [Function],
"createRemoveTokenForOrg": [Function],
"createRemoveTokenForRepo": [Function],
"deleteArtifact": [Function],
"deleteOrgSecret": [Function],
"deleteRepoSecret": [Function],
"deleteSecretFromRepo": [Function],
"deleteSelfHostedRunnerFromOrg": [Function],
"deleteSelfHostedRunnerFromRepo": [Function],
"deleteWorkflowRunLogs": [Function],
"downloadArtifact": [Function],
"downloadJobLogsForWorkflowRun": [Function],
"downloadWorkflowJobLogs": [Function],
"downloadWorkflowRunLogs": [Function],
"getArtifact": [Function],
"getJobForWorkflowRun": [Function],
"getOrgPublicKey": [Function],
"getOrgSecret": [Function],
"getPublicKey": [Function],
"getRepoPublicKey": [Function],
"getRepoSecret": [Function],
"getSecret": [Function],
"getSelfHostedRunner": [Function],
"getSelfHostedRunnerForOrg": [Function],
"getSelfHostedRunnerForRepo": [Function],
"getWorkflow": [Function],
"getWorkflowJob": [Function],
"getWorkflowRun": [Function],
"getWorkflowRunUsage": [Function],
"getWorkflowUsage": [Function],
"listArtifactsForRepo": [Function],
"listDownloadsForSelfHostedRunnerApplication": [Function],
"listJobsForWorkflowRun": [Function],
"listOrgSecrets": [Function],
"listRepoSecrets": [Function],
"listRepoWorkflowRuns": [Function],
"listRepoWorkflows": [Function],
"listRunnerApplicationsForOrg": [Function],
"listRunnerApplicationsForRepo": [Function],
"listSecretsForRepo": [Function],
"listSelectedReposForOrgSecret": [Function],
"listSelfHostedRunnersForOrg": [Function],
"listSelfHostedRunnersForRepo": [Function],
"listWorkflowJobLogs": [Function],
"listWorkflowRunArtifacts": [Function],
"listWorkflowRunLogs": [Function],
"listWorkflowRuns": [Function],
"listWorkflowRunsForRepo": [Function],
"reRunWorkflow": [Function],
"removeSelectedRepoFromOrgSecret": [Function],
"removeSelfHostedRunner": [Function],
"setSelectedReposForOrgSecret": [Function],
},
"activity": Object {
"checkRepoIsStarredByAuthenticatedUser": [Function],
"checkStarringRepo": [Function],
"deleteRepoSubscription": [Function],
"deleteThreadSubscription": [Function],
"getFeeds": [Function],
"getRepoSubscription": [Function],
"getThread": [Function],
"getThreadSubscription": [Function],
"getThreadSubscriptionForAuthenticatedUser": [Function],
"listEventsForAuthenticatedUser": [Function],
"listEventsForOrg": [Function],
"listEventsForUser": [Function],
"listFeeds": [Function],
"listNotifications": [Function],
"listNotificationsForAuthenticatedUser": [Function],
"listNotificationsForRepo": [Function],
"listOrgEventsForAuthenticatedUser": [Function],
"listPublicEvents": [Function],
"listPublicEventsForOrg": [Function],
"listPublicEventsForRepoNetwork": [Function],
"listPublicEventsForUser": [Function],
"listPublicOrgEvents": [Function],
"listReceivedEventsForUser": [Function],
"listReceivedPublicEventsForUser": [Function],
"listRepoEvents": [Function],
"listRepoNotificationsForAuthenticatedUser": [Function],
"listReposStarredByAuthenticatedUser": [Function],
"listReposStarredByUser": [Function],
"listReposWatchedByUser": [Function],
"listStargazersForRepo": [Function],
"listWatchedReposForAuthenticatedUser": [Function],
"listWatchersForRepo": [Function],
"markAsRead": [Function],
"markNotificationsAsRead": [Function],
"markNotificationsAsReadForRepo": [Function],
"markRepoNotificationsAsRead": [Function],
"markThreadAsRead": [Function],
"setRepoSubscription": [Function],
"setThreadSubscription": [Function],
"starRepo": [Function],
"starRepoForAuthenticatedUser": [Function],
"unstarRepo": [Function],
"unstarRepoForAuthenticatedUser": [Function],
},
"apps": Object {
"addRepoToInstallation": [Function],
"checkAccountIsAssociatedWithAny": [Function],
"checkAccountIsAssociatedWithAnyStubbed": [Function],
"checkToken": [Function],
"createContentAttachment": [Function],
"createFromManifest": [Function],
"createInstallationAccessToken": [Function],
"createInstallationToken": [Function],
"deleteAuthorization": [Function],
"deleteInstallation": [Function],
"deleteToken": [Function],
"getAuthenticated": [Function],
"getBySlug": [Function],
"getInstallation": [Function],
"getOrgInstallation": [Function],
"getRepoInstallation": [Function],
"getSubscriptionPlanForAccount": [Function],
"getSubscriptionPlanForAccountStubbed": [Function],
"getUserInstallation": [Function],
"listAccountsForPlan": [Function],
"listAccountsForPlanStubbed": [Function],
"listAccountsUserOrOrgOnPlan": [Function],
"listAccountsUserOrOrgOnPlanStubbed": [Function],
"listInstallationReposForAuthenticatedUser": [Function],
"listInstallations": [Function],
"listInstallationsForAuthenticatedUser": [Function],
"listMarketplacePurchasesForAuthenticatedUser": [Function],
"listMarketplacePurchasesForAuthenticatedUserStubbed": [Function],
"listPlans": [Function],
"listPlansStubbed": [Function],
"listRepos": [Function],
"listReposAccessibleToInstallation": [Function],
"listSubscriptionsForAuthenticatedUser": [Function],
"listSubscriptionsForAuthenticatedUserStubbed": [Function],
"removeRepoFromInstallation": [Function],
"resetToken": [Function],
"revokeInstallationAccessToken": [Function],
"revokeInstallationToken": [Function],
"suspendInstallation": [Function],
"unsuspendInstallation": [Function],
},
"auth": [Function],
"checks": Object {
"create": [Function],
"createSuite": [Function],
"get": [Function],
"getSuite": [Function],
"listAnnotations": [Function],
"listForRef": [Function],
"listForSuite": [Function],
"listSuitesForRef": [Function],
"rerequestSuite": [Function],
"setSuitesPreferences": [Function],
"update": [Function],
},
"codeScanning": Object {
"getAlert": [Function],
"listAlertsForRepo": [Function],
},
"codesOfConduct": Object {
"getAllCodesOfConduct": [Function],
"getConductCode": [Function],
"getForRepo": [Function],
"listConductCodes": [Function],
},
"emojis": Object {
"get": [Function],
},
"gists": Object {
"checkIsStarred": [Function],
"create": [Function],
"createComment": [Function],
"delete": [Function],
"deleteComment": [Function],
"fork": [Function],
"get": [Function],
"getComment": [Function],
"getRevision": [Function],
"list": [Function],
"listComments": [Function],
"listCommits": [Function],
"listForUser": [Function],
"listForks": [Function],
"listPublic": [Function],
"listPublicForUser": [Function],
"listStarred": [Function],
"star": [Function],
"unstar": [Function],
"update": [Function],
"updateComment": [Function],
},
"git": Object {
"createBlob": [Function],
"createCommit": [Function],
"createRef": [Function],
"createTag": [Function],
"createTree": [Function],
"deleteRef": [Function],
"getBlob": [Function],
"getCommit": [Function],
"getRef": [Function],
"getTag": [Function],
"getTree": [Function],
"listMatchingRefs": [Function],
"updateRef": [Function],
},
"gitignore": Object {
"getAllTemplates": [Function],
"getTemplate": [Function],
"listTemplates": [Function],
},
"graphql": [Function],
"hook": [Function],
"interactions": Object {
"addOrUpdateRestrictionsForOrg": [Function],
"addOrUpdateRestrictionsForRepo": [Function],
"getRestrictionsForOrg": [Function],
"getRestrictionsForRepo": [Function],
"removeRestrictionsForOrg": [Function],
"removeRestrictionsForRepo": [Function],
"setRestrictionsForOrg": [Function],
"setRestrictionsForRepo": [Function],
},
"issues": Object {
"addAssignees": [Function],
"addLabels": [Function],
"checkAssignee": [Function],
"checkUserCanBeAssigned": [Function],
"create": [Function],
"createComment": [Function],
"createLabel": [Function],
"createMilestone": [Function],
"deleteComment": [Function],
"deleteLabel": [Function],
"deleteMilestone": [Function],
"get": [Function],
"getComment": [Function],
"getEvent": [Function],
"getLabel": [Function],
"getMilestone": [Function],
"list": [Function],
"listAssignees": [Function],
"listComments": [Function],
"listCommentsForRepo": [Function],
"listEvents": [Function],
"listEventsForRepo": [Function],
"listEventsForTimeline": [Function],
"listForAuthenticatedUser": [Function],
"listForOrg": [Function],
"listForRepo": [Function],
"listLabelsForMilestone": [Function],
"listLabelsForRepo": [Function],
"listLabelsOnIssue": [Function],
"listMilestones": [Function],
"listMilestonesForRepo": [Function],
"lock": [Function],
"removeAllLabels": [Function],
"removeAssignees": [Function],
"removeLabel": [Function],
"removeLabels": [Function],
"replaceAllLabels": [Function],
"replaceLabels": [Function],
"setLabels": [Function],
"unlock": [Function],
"update": [Function],
"updateComment": [Function],
"updateLabel": [Function],
"updateMilestone": [Function],
},
"licenses": Object {
"get": [Function],
"getAllCommonlyUsed": [Function],
"getForRepo": [Function],
"listCommonlyUsed": [Function],
},
"log": Object {
"debug": [Function],
"error": [Function],
"info": [Function],
"warn": [Function],
},
"markdown": Object {
"render": [Function],
"renderRaw": [Function],
},
"meta": Object {
"get": [Function],
},
"migrations": Object {
"cancelImport": [Function],
"deleteArchiveForAuthenticatedUser": [Function],
"deleteArchiveForOrg": [Function],
"downloadArchiveForOrg": [Function],
"getArchiveForAuthenticatedUser": [Function],
"getCommitAuthors": [Function],
"getImportProgress": [Function],
"getImportStatus": [Function],
"getLargeFiles": [Function],
"getStatusForAuthenticatedUser": [Function],
"getStatusForOrg": [Function],
"listForAuthenticatedUser": [Function],
"listForOrg": [Function],
"listReposForOrg": [Function],
"listReposForUser": [Function],
"mapCommitAuthor": [Function],
"setLfsPreference": [Function],
"startForAuthenticatedUser": [Function],
"startForOrg": [Function],
"startImport": [Function],
"unlockRepoForAuthenticatedUser": [Function],
"unlockRepoForOrg": [Function],
"updateImport": [Function],
},
"orgs": Object {
"addOrUpdateMembership": [Function],
"blockUser": [Function],
"checkBlockedUser": [Function],
"checkMembership": [Function],
"checkMembershipForUser": [Function],
"checkPublicMembership": [Function],
"checkPublicMembershipForUser": [Function],
"concealMembership": [Function],
"convertMemberToOutsideCollaborator": [Function],
"createHook": [Function],
"createInvitation": [Function],
"createWebhook": [Function],
"deleteHook": [Function],
"deleteWebhook": [Function],
"get": [Function],
"getHook": [Function],
"getMembership": [Function],
"getMembershipForAuthenticatedUser": [Function],
"getMembershipForUser": [Function],
"getWebhook": [Function],
"list": [Function],
"listAppInstallations": [Function],
"listBlockedUsers": [Function],
"listForAuthenticatedUser": [Function],
"listForUser": [Function],
"listHooks": [Function],
"listInstallations": [Function],
"listInvitationTeams": [Function],
"listMembers": [Function],
"listMemberships": [Function],
"listMembershipsForAuthenticatedUser": [Function],
"listOutsideCollaborators": [Function],
"listPendingInvitations": [Function],
"listPublicMembers": [Function],
"listWebhooks": [Function],
"pingHook": [Function],
"pingWebhook": [Function],
"publicizeMembership": [Function],
"removeMember": [Function],
"removeMembership": [Function],
"removeMembershipForUser": [Function],
"removeOutsideCollaborator": [Function],
"removePublicMembershipForAuthenticatedUser": [Function],
"setMembershipForUser": [Function],
"setPublicMembershipForAuthenticatedUser": [Function],
"unblockUser": [Function],
"update": [Function],
"updateHook": [Function],
"updateMembership": [Function],
"updateMembershipForAuthenticatedUser": [Function],
"updateWebhook": [Function],
},
"paginate": [Function],
"projects": Object {
"addCollaborator": [Function],
"createCard": [Function],
"createColumn": [Function],
"createForAuthenticatedUser": [Function],
"createForOrg": [Function],
"createForRepo": [Function],
"delete": [Function],
"deleteCard": [Function],
"deleteColumn": [Function],
"get": [Function],
"getCard": [Function],
"getColumn": [Function],
"getPermissionForUser": [Function],
"listCards": [Function],
"listCollaborators": [Function],
"listColumns": [Function],
"listForOrg": [Function],
"listForRepo": [Function],
"listForUser": [Function],
"moveCard": [Function],
"moveColumn": [Function],
"removeCollaborator": [Function],
"reviewUserPermissionLevel": [Function],
"update": [Function],
"updateCard": [Function],
"updateColumn": [Function],
},
"pulls": Object {
"checkIfMerged": [Function],
"create": [Function],
"createComment": [Function],
"createReplyForReviewComment": [Function],
"createReview": [Function],
"createReviewComment": [Function],
"createReviewCommentReply": [Function],
"createReviewRequest": [Function],
"deleteComment": [Function],
"deletePendingReview": [Function],
"deleteReviewComment": [Function],
"deleteReviewRequest": [Function],
"dismissReview": [Function],
"get": [Function],
"getComment": [Function],
"getCommentsForReview": [Function],
"getReview": [Function],
"getReviewComment": [Function],
"list": [Function],
"listComments": [Function],
"listCommentsForRepo": [Function],
"listCommentsForReview": [Function],
"listCommits": [Function],
"listFiles": [Function],
"listRequestedReviewers": [Function],
"listReviewComments": [Function],
"listReviewCommentsForRepo": [Function],
"listReviewRequests": [Function],
"listReviews": [Function],
"merge": [Function],
"removeRequestedReviewers": [Function],
"requestReviewers": [Function],
"submitReview": [Function],
"update": [Function],
"updateBranch": [Function],
"updateComment": [Function],
"updateReview": [Function],
"updateReviewComment": [Function],
},
"rateLimit": Object {
"get": [Function],
},
"reactions": Object {
"createForCommitComment": [Function],
"createForIssue": [Function],
"createForIssueComment": [Function],
"createForPullRequestReviewComment": [Function],
"createForTeamDiscussionCommentInOrg": [Function],
"createForTeamDiscussionInOrg": [Function],
"delete": [Function],
"deleteForCommitComment": [Function],
"deleteForIssue": [Function],
"deleteForIssueComment": [Function],
"deleteForPullRequestComment": [Function],
"deleteForTeamDiscussion": [Function],
"deleteForTeamDiscussionComment": [Function],
"deleteLegacy": [Function],
"listForCommitComment": [Function],
"listForIssue": [Function],
"listForIssueComment": [Function],
"listForPullRequestReviewComment": [Function],
"listForTeamDiscussionCommentInOrg": [Function],
"listForTeamDiscussionInOrg": [Function],
},
"repos": Object {
"acceptInvitation": [Function],
"addAppAccessRestrictions": [Function],
"addCollaborator": [Function],
"addDeployKey": [Function],
"addProtectedBranchAdminEnforcement": [Function],
"addProtectedBranchAppRestrictions": [Function],
"addProtectedBranchRequiredSignatures": [Function],
"addProtectedBranchRequiredStatusChecksContexts": [Function],
"addProtectedBranchTeamRestrictions": [Function],
"addProtectedBranchUserRestrictions": [Function],
"addStatusCheckContexts": [Function],
"addTeamAccessRestrictions": [Function],
"addUserAccessRestrictions": [Function],
"checkCollaborator": [Function],
"checkVulnerabilityAlerts": [Function],
"compareCommits": [Function],
"createCommitComment": [Function],
"createCommitSignatureProtection": [Function],
"createCommitStatus": [Function],
"createDeployKey": [Function],
"createDeployment": [Function],
"createDeploymentStatus": [Function],
"createDispatchEvent": [Function],
"createForAuthenticatedUser": [Function],
"createFork": [Function],
"createHook": [Function],
"createInOrg": [Function],
"createOrUpdateFile": [Function],
"createOrUpdateFileContents": [Function],
"createPagesSite": [Function],
"createRelease": [Function],
"createStatus": [Function],
"createUsingTemplate": [Function],
"createWebhook": [Function],
"declineInvitation": [Function],
"delete": [Function],
"deleteAccessRestrictions": [Function],
"deleteAdminBranchProtection": [Function],
"deleteBranchProtection": [Function],
"deleteCommitComment": [Function],
"deleteCommitSignatureProtection": [Function],
"deleteDeployKey": [Function],
"deleteDeployment": [Function],
"deleteDownload": [Function],
"deleteFile": [Function],
"deleteHook": [Function],
"deleteInvitation": [Function],
"deletePagesSite": [Function],
"deletePullRequestReviewProtection": [Function],
"deleteRelease": [Function],
"deleteReleaseAsset": [Function],
"deleteWebhook": [Function],
"disableAutomatedSecurityFixes": [Function],
"disablePagesSite": [Function],
"disableVulnerabilityAlerts": [Function],
"downloadArchive": [Function],
"enableAutomatedSecurityFixes": [Function],
"enablePagesSite": [Function],
"enableVulnerabilityAlerts": [Function],
"get": [Function],
"getAccessRestrictions": [Function],
"getAdminBranchProtection": [Function],
"getAllStatusCheckContexts": [Function],
"getAllTopics": [Function],
"getAppsWithAccessToProtectedBranch": [Function],
"getArchiveLink": [Function],
"getBranch": [Function],
"getBranchProtection": [Function],
"getClones": [Function],
"getCodeFrequencyStats": [Function],
"getCollaboratorPermissionLevel": [Function],
"getCombinedStatusForRef": [Function],
"getCommit": [Function],
"getCommitActivityStats": [Function],
"getCommitComment": [Function],
"getCommitSignatureProtection": [Function],
"getCommunityProfileMetrics": [Function],
"getContent": [Function],
"getContents": [Function],
"getContributorsStats": [Function],
"getDeployKey": [Function],
"getDeployment": [Function],
"getDeploymentStatus": [Function],
"getDownload": [Function],
"getHook": [Function],
"getLatestPagesBuild": [Function],
"getLatestRelease": [Function],
"getPages": [Function],
"getPagesBuild": [Function],
"getParticipationStats": [Function],
"getProtectedBranchAdminEnforcement": [Function],
"getProtectedBranchPullRequestReviewEnforcement": [Function],
"getProtectedBranchRequiredSignatures": [Function],
"getProtectedBranchRequiredStatusChecks": [Function],
"getProtectedBranchRestrictions": [Function],
"getPullRequestReviewProtection": [Function],
"getPunchCardStats": [Function],
"getReadme": [Function],
"getRelease": [Function],
"getReleaseAsset": [Function],
"getReleaseByTag": [Function],
"getStatusChecksProtection": [Function],
"getTeamsWithAccessToProtectedBranch": [Function],
"getTopPaths": [Function],
"getTopReferrers": [Function],
"getUsersWithAccessToProtectedBranch": [Function],
"getViews": [Function],
"getWebhook": [Function],
"list": [Function],
"listAssetsForRelease": [Function],
"listBranches": [Function],
"listBranchesForHeadCommit": [Function],
"listCollaborators": [Function],
"listCommentsForCommit": [Function],
"listCommitComments": [Function],
"listCommitCommentsForRepo": [Function],
"listCommitStatusesForRef": [Function],
"listCommits": [Function],
"listContributors": [Function],
"listDeployKeys": [Function],
"listDeploymentStatuses": [Function],
"listDeployments": [Function],
"listDownloads": [Function],
"listForAuthenticatedUser": [Function],
"listForOrg": [Function],
"listForUser": [Function],
"listForks": [Function],
"listHooks": [Function],
"listInvitations": [Function],
"listInvitationsForAuthenticatedUser": [Function],
"listLanguages": [Function],
"listPagesBuilds": [Function],
"listProtectedBranchRequiredStatusChecksContexts": [Function],
"listPublic": [Function],
"listPullRequestsAssociatedWithCommit": [Function],
"listReleaseAssets": [Function],
"listReleases": [Function],
"listStatusesForRef": [Function],
"listTags": [Function],
"listTeams": [Function],
"listTopics": [Function],
"listWebhooks": [Function],
"merge": [Function],
"pingHook": [Function],
"pingWebhook": [Function],
"removeAppAccessRestrictions": [Function],
"removeBranchProtection": [Function],
"removeCollaborator": [Function],
"removeDeployKey": [Function],
"removeProtectedBranchAdminEnforcement": [Function],
"removeProtectedBranchAppRestrictions": [Function],
"removeProtectedBranchPullRequestReviewEnforcement": [Function],
"removeProtectedBranchRequiredSignatures": [Function],
"removeProtectedBranchRequiredStatusChecks": [Function],
"removeProtectedBranchRequiredStatusChecksContexts": [Function],
"removeProtectedBranchRestrictions": [Function],
"removeProtectedBranchTeamRestrictions": [Function],
"removeProtectedBranchUserRestrictions": [Function],
"removeStatusCheckContexts": [Function],
"removeStatusCheckProtection": [Function],
"removeTeamAccessRestrictions": [Function],
"removeUserAccessRestrictions": [Function],
"replaceAllTopics": [Function],
"replaceProtectedBranchAppRestrictions": [Function],
"replaceProtectedBranchRequiredStatusChecksContexts": [Function],
"replaceProtectedBranchTeamRestrictions": [Function],
"replaceProtectedBranchUserRestrictions": [Function],
"replaceTopics": [Function],
"requestPageBuild": [Function],
"requestPagesBuild": [Function],
"retrieveCommunityProfileMetrics": [Function],
"setAdminBranchProtection": [Function],
"setAppAccessRestrictions": [Function],
"setStatusCheckContexts": [Function],
"setTeamAccessRestrictions": [Function],
"setUserAccessRestrictions": [Function],
"testPushHook": [Function],
"testPushWebhook": [Function],
"transfer": [Function],
"update": [Function],
"updateBranchProtection": [Function],
"updateCommitComment": [Function],
"updateHook": [Function],
"updateInformationAboutPagesSite": [Function],
"updateInvitation": [Function],
"updateProtectedBranchPullRequestReviewEnforcement": [Function],
"updateProtectedBranchRequiredStatusChecks": [Function],
"updatePullRequestReviewProtection": [Function],
"updateRelease": [Function],
"updateReleaseAsset": [Function],
"updateStatusCheckPotection": [Function],
"updateWebhook": [Function],
"uploadReleaseAsset": [Function],
},
"request": [Function],
"search": Object {
"code": [Function],
"commits": [Function],
"issuesAndPullRequests": [Function],
"labels": [Function],
"repos": [Function],
"topics": [Function],
"users": [Function],
},
"teams": Object {
"addOrUpdateMembershipForUserInOrg": [Function],
"addOrUpdateMembershipInOrg": [Function],
"addOrUpdateProjectInOrg": [Function],
"addOrUpdateProjectPermissionsInOrg": [Function],
"addOrUpdateRepoInOrg": [Function],
"addOrUpdateRepoPermissionsInOrg": [Function],
"checkManagesRepoInOrg": [Function],
"checkPermissionsForProjectInOrg": [Function],
"checkPermissionsForRepoInOrg": [Function],
"create": [Function],
"createDiscussionCommentInOrg": [Function],
"createDiscussionInOrg": [Function],
"deleteDiscussionCommentInOrg": [Function],
"deleteDiscussionInOrg": [Function],
"deleteInOrg": [Function],
"getByName": [Function],
"getDiscussionCommentInOrg": [Function],
"getDiscussionInOrg": [Function],
"getMembershipForUserInOrg": [Function],
"getMembershipInOrg": [Function],
"list": [Function],
"listChildInOrg": [Function],
"listDiscussionCommentsInOrg": [Function],
"listDiscussionsInOrg": [Function],
"listForAuthenticatedUser": [Function],
"listMembersInOrg": [Function],
"listPendingInvitationsInOrg": [Function],
"listProjectsInOrg": [Function],
"listReposInOrg": [Function],
"removeMembershipForUserInOrg": [Function],
"removeMembershipInOrg": [Function],
"removeProjectInOrg": [Function],
"removeRepoInOrg": [Function],
"reviewProjectInOrg": [Function],
"updateDiscussionCommentInOrg": [Function],
"updateDiscussionInOrg": [Function],
"updateInOrg": [Function],
},
"users": Object {
"addEmailForAuthenticated": [Function],
"addEmails": [Function],
"block": [Function],
"checkBlocked": [Function],
"checkFollowing": [Function],
"checkFollowingForUser": [Function],
"checkPersonIsFollowedByAuthenticated": [Function],
"createGpgKey": [Function],
"createGpgKeyForAuthenticated": [Function],
"createPublicKey": [Function],
"createPublicSshKeyForAuthenticated": [Function],
"deleteEmailForAuthenticated": [Function],
"deleteEmails": [Function],
"deleteGpgKey": [Function],
"deleteGpgKeyForAuthenticated": [Function],
"deletePublicKey": [Function],
"deletePublicSshKeyForAuthenticated": [Function],
"follow": [Function],
"getAuthenticated": [Function],
"getByUsername": [Function],
"getContextForUser": [Function],
"getGpgKey": [Function],
"getGpgKeyForAuthenticated": [Function],
"getPublicKey": [Function],
"getPublicSshKeyForAuthenticated": [Function],
"list": [Function],
"listBlocked": [Function],
"listBlockedByAuthenticated": [Function],
"listEmails": [Function],
"listEmailsForAuthenticated": [Function],
"listFollowedByAuthenticated": [Function],
"listFollowersForAuthenticatedUser": [Function],
"listFollowersForUser": [Function],
"listFollowingForAuthenticatedUser": [Function],
"listFollowingForUser": [Function],
"listGpgKeys": [Function],
"listGpgKeysForAuthenticated": [Function],
"listGpgKeysForUser": [Function],
"listPublicEmails": [Function],
"listPublicEmailsForAuthenticated": [Function],
"listPublicKeys": [Function],
"listPublicKeysForUser": [Function],
"listPublicSshKeysForAuthenticated": [Function],
"setPrimaryEmailVisibilityForAuthenticated": [Function],
"togglePrimaryEmailVisibility": [Function],
"unblock": [Function],
"unfollow": [Function],
"updateAuthenticated": [Function],
},
},
},
"graphStylingPane": GraphStylingPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
@@ -429,7 +1196,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
"isAutoscaleDefaultEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isGitHubPaneEnabled": [Function],
"isHostedDataExplorerEnabled": [Function],
"isLastCollection": [Function],
"isLastNonEmptyDatabase": [Function],
@@ -447,9 +1213,14 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
"isSparkEnabledForAccount": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
"junoClient": JunoClient {
"cachedPinnedRepos": [Function],
"databaseAccount": undefined,
},
"memoryUsageInfo": [Function],
"notebookBasePath": [Function],
"notebookServerInfo": [Function],
"onGitHubClientError": [Function],
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function],
@@ -525,7 +1296,7 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
<Stack
className="panelInfoErrorContainer"
horizontal={true}
verticalAlign="start"
verticalAlign="center"
>
<div
className="ms-Stack panelInfoErrorContainer css-140"

View File

@@ -165,7 +165,7 @@ export function convertEntityToNewDocument(entity: Entities.ITableEntityForTable
$id: entity.RowKey._,
id: entity.RowKey._,
};
for (var property in entity) {
for (const property in entity) {
if (
property !== Constants.EntityKeyNames.PartitionKey &&
property !== Constants.EntityKeyNames.RowKey &&
@@ -176,18 +176,30 @@ export function convertEntityToNewDocument(entity: Entities.ITableEntityForTable
property !== keyProperties.attachments &&
property !== keyProperties.Id2
) {
if (entity[property].$ === Constants.TableType.DateTime) {
// Convert javascript date back to ticks with 20 zeros padding
document[property] = {
$t: (<any>DataTypes)[entity[property].$],
$v: DateTimeUtilities.convertJSDateToTicksWithPadding(entity[property]._),
};
} else {
document[property] = {
$t: (<any>DataTypes)[entity[property].$],
$v: entity[property]._,
};
const propertyValue = entity[property]._;
let parsedValue;
switch (entity[property].$) {
case Constants.TableType.DateTime:
parsedValue = DateTimeUtilities.convertJSDateToTicksWithPadding(propertyValue);
break;
case Constants.TableType.Boolean:
parsedValue = propertyValue.toLowerCase() === "true";
break;
case Constants.TableType.Int32:
case Constants.TableType.Int64:
parsedValue = parseInt(propertyValue, 10);
break;
case Constants.TableType.Double:
parsedValue = parseFloat(propertyValue);
break;
default:
parsedValue = propertyValue;
}
document[property] = {
$t: (<any>DataTypes)[entity[property].$],
$v: parsedValue,
};
}
}
return document;

View File

@@ -5,8 +5,12 @@ import StyleIcon from "../../../images/Style.svg";
import { DatabaseAccount } from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import { GraphAccessor, GraphExplorerError } from "../Graph/GraphExplorerComponent/GraphExplorer";
import { GraphExplorerAdapter } from "../Graph/GraphExplorerComponent/GraphExplorerAdapter";
import {
GraphAccessor,
GraphExplorer,
GraphExplorerError,
GraphExplorerProps,
} from "../Graph/GraphExplorerComponent/GraphExplorer";
import { ContextualPaneBase } from "../Panes/ContextualPaneBase";
import GraphStylingPane from "../Panes/GraphStylingPane";
import { NewVertexPanel } from "../Panes/NewVertexPanel/NewVertexPanel";
@@ -36,15 +40,13 @@ interface GraphTabOptions extends ViewModels.TabOptions {
}
export default class GraphTab extends TabsBase {
public readonly html =
'<div class="graphExplorerContainer" role="tabpanel" data-bind="react:graphExplorerAdapter, attr: {id: tabId}"></div>';
// Graph default configuration
public static readonly DEFAULT_NODE_CAPTION = "id";
private static readonly LINK_COLOR = "#aaa";
private static readonly NODE_SIZE = 10;
private static readonly NODE_COLOR = "orange";
private static readonly LINK_WIDTH = 1;
private graphExplorerAdapter: GraphExplorerAdapter;
private graphExplorerProps: GraphExplorerProps;
private isNewVertexDisabled: ko.Observable<boolean>;
private isPropertyEditing: ko.Observable<boolean>;
private isGraphDisplayed: ko.Observable<boolean>;
@@ -70,7 +72,7 @@ export default class GraphTab extends TabsBase {
this.graphConfig = GraphTab.createGraphConfig();
// TODO Merge this with this.graphConfig
this.graphConfigUiData = GraphTab.createGraphConfigUiData(this.graphConfig);
this.graphExplorerAdapter = new GraphExplorerAdapter({
this.graphExplorerProps = {
onGraphAccessorCreated: (instance: GraphAccessor): void => {
this.graphAccessor = instance;
},
@@ -89,8 +91,9 @@ export default class GraphTab extends TabsBase {
onResetDefaultGraphConfigValues: () => this.setDefaultGraphConfigValues(),
graphConfig: this.graphConfig,
graphConfigUiData: this.graphConfigUiData,
onIsFilterQueryLoading: (isFilterQueryLoading: boolean): void => this.isFilterQueryLoading(isFilterQueryLoading),
onIsValidQuery: (isValidQuery: boolean): void => this.isValidQuery(isValidQuery),
onIsFilterQueryLoadingChange: (isFilterQueryLoading: boolean): void =>
this.isFilterQueryLoading(isFilterQueryLoading),
onIsValidQueryChange: (isValidQuery: boolean): void => this.isValidQuery(isValidQuery),
collectionPartitionKeyProperty: options.collectionPartitionKeyProperty,
graphBackendEndpoint: GraphTab.getGremlinEndpoint(options.account),
databaseId: options.databaseId,
@@ -103,7 +106,7 @@ export default class GraphTab extends TabsBase {
}
},
resourceId: options.account.id,
});
};
this.isFilterQueryLoading = ko.observable(false);
this.isValidQuery = ko.observable(true);
@@ -115,6 +118,14 @@ export default class GraphTab extends TabsBase {
: `${account.name}.graphs.azure.com:443/`;
}
public render(): JSX.Element {
return (
<div className="graphExplorerContainer" role="tabpanel" id={this.tabId}>
<GraphExplorer {...this.graphExplorerProps} />
</div>
);
}
public onTabClick(): void {
super.onTabClick();
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Graph);

View File

@@ -205,9 +205,8 @@ export default class Database implements ViewModels.Database {
this.deleteCollectionsFromList(deltaCollections.toDelete);
}
public openAddCollection(database: Database, event: MouseEvent) {
database.container.addCollectionPane.databaseId(database.id());
database.container.addCollectionPane.open();
public openAddCollection(database: Database) {
database.container.openAddCollectionPanel(database.id());
}
public findCollectionWithId(collectionId: string): ViewModels.Collection {

View File

@@ -197,7 +197,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
className: "databaseHeader",
children: [],
isSelected: () => this.isDataNodeSelected(database.id()),
contextMenu: ResourceTreeContextMenuButtonFactory.createDatabaseContextMenu(this.container),
contextMenu: ResourceTreeContextMenuButtonFactory.createDatabaseContextMenu(this.container, database.id()),
onClick: async (isExpanded) => {
// Rewritten version of expandCollapseDatabase():
if (isExpanded) {
@@ -607,7 +607,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
gitHubNotebooksTree.contextMenu = [
{
label: "Manage GitHub settings",
onClick: () => this.container.gitHubReposPane.open(),
onClick: () => this.container.openGitHubReposPanel("Manage GitHub settings"),
},
{
label: "Disconnect from GitHub",

View File

@@ -52,7 +52,6 @@ import { useConfig } from "./hooks/useConfig";
import { useKnockoutExplorer } from "./hooks/useKnockoutExplorer";
import { useSidePanel } from "./hooks/useSidePanel";
import { useTabs } from "./hooks/useTabs";
import { KOCommentEnd, KOCommentIfStart } from "./koComment";
import "./Libs/jquery";
import "./Shared/appInsights";
import { userContext } from "./UserContext";
@@ -231,9 +230,6 @@ const App: React.FunctionComponent = () => {
<div data-bind='component: { name: "add-collection-pane", params: { data: addCollectionPane} }' />
<div data-bind='component: { name: "graph-styling-pane", params: { data: graphStylingPane} }' />
<div data-bind='component: { name: "cassandra-add-collection-pane", params: { data: cassandraAddCollectionPane} }' />
<KOCommentIfStart if="isGitHubPaneEnabled" />
<div data-bind='component: { name: "github-repos-pane", params: { data: gitHubReposPane } }' />
<KOCommentEnd />
{showDialog && <Dialog {...dialogProps} />}
</div>
);

View File

@@ -115,6 +115,7 @@ export enum Action {
NotebooksGalleryFavoritesCount,
NotebooksGalleryPublishedCount,
SelfServe,
ExpandAddCollectionPaneAdvancedSection,
}
export const ActionModifiers = {

View File

@@ -19,7 +19,7 @@ interface UserContext {
readonly quotaId?: string;
// API Type is not yet provided by ARM. You need to manually inspect all the capabilities+kind so we abstract that logic in userContext
// This is coming in a future Cosmos ARM API version as a prperty on databaseAccount
apiType?: ApiType;
apiType: ApiType;
readonly isTryCosmosDBSubscription?: boolean;
readonly portalEnv?: PortalEnv;
readonly features: Features;

30
src/Utils/APITypeUtils.ts Normal file
View File

@@ -0,0 +1,30 @@
import { userContext } from "../UserContext";
export const getCollectionName = (isPlural?: boolean): string => {
let collectionName: string;
let unknownApiType: never;
switch (userContext.apiType) {
case "SQL":
collectionName = "Container";
break;
case "Mongo":
collectionName = "Collection";
break;
case "Cassandra":
case "Tables":
collectionName = "Table";
break;
case "Gremlin":
collectionName = "Graph";
break;
default:
unknownApiType = userContext.apiType;
throw new Error(`Unknown API type: ${unknownApiType}`);
}
if (isPlural) {
collectionName += "s";
}
return collectionName;
};

View File

@@ -1,4 +1,5 @@
import * as Constants from "../Shared/Constants";
import { getCollectionName } from "../Utils/APITypeUtils";
import * as AutoPilotUtils from "../Utils/AutoPilotUtils";
interface ComputeRUUsagePriceHourlyArgs {
@@ -10,7 +11,7 @@ interface ComputeRUUsagePriceHourlyArgs {
}
export const estimatedCostDisclaimer =
"*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account";
"This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account";
/**
* Anything that is not a number should return 0
@@ -161,7 +162,7 @@ export function getAutoPilotV3SpendHtml(maxAutoPilotThroughputSet: number, isDat
return "";
}
const resource: string = isDatabaseThroughput ? "database" : "container";
const resource: string = isDatabaseThroughput ? "database" : getCollectionName().toLocaleLowerCase();
return `Your ${resource} throughput will automatically scale from <b>${AutoPilotUtils.getMinRUsBasedOnUserInput(
maxAutoPilotThroughputSet
)} RU/s (10% of max RU/s) - ${maxAutoPilotThroughputSet} RU/s</b> based on usage. <br /><br />After the first ${AutoPilotUtils.getStorageBasedOnUserInput(
@@ -227,7 +228,7 @@ export function getEstimatedSpendHtml(
`${currencySign}${calculateEstimateNumber(monthlyPrice)} monthly </b> ` +
`(${regions} ${regions === 1 ? "region" : "regions"}, ${throughput}RU/s, ${currencySign}${pricePerRu}/RU)` +
`<p style='padding: 10px 0px 0px 0px;'>` +
`<em>${estimatedCostDisclaimer}</em></p>`
`<em>*${estimatedCostDisclaimer}</em></p>`
);
}
@@ -261,11 +262,10 @@ export function getUpsellMessage(
serverId = "default",
isFreeTier = false,
isFirstResourceCreated = false,
defaultExperience: string,
isCollection: boolean
): string {
if (isFreeTier) {
const collectionName = getCollectionName(defaultExperience);
const collectionName = getCollectionName().toLocaleLowerCase();
const resourceType = isCollection ? collectionName : "database";
return isFirstResourceCreated
? `The free tier discount of 400 RU/s has already been applied to a database or ${collectionName} in this account. Billing will apply to this ${resourceType} after it is created.`
@@ -277,22 +277,8 @@ export function getUpsellMessage(
price = Constants.OfferPricing.MonthlyPricing.mooncake.Standard.StartingPrice;
}
return `Start at ${getCurrencySign(serverId)}${price}/mo per database, multiple containers included`;
}
}
export function getCollectionName(defaultExperience: string): string {
switch (defaultExperience) {
case "SQL":
return "container";
case "Mongo":
return "collection";
case "Tables":
case "Cassandra":
return "table";
case "Gremlin":
return "graph";
default:
throw Error("unknown API type");
return `Start at ${getCurrencySign(serverId)}${price}/mo per database, multiple ${getCollectionName(
true
).toLocaleLowerCase()} included`;
}
}

View File

@@ -1,20 +0,0 @@
/* eslint-disable react/prop-types */
import React, { useEffect, useRef } from "react";
export const KOCommentIfStart: React.FunctionComponent<{ if: string }> = (props) => {
const el = useRef();
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(el.current as any).outerHTML = `<!-- ko if: ${props.if} -->`;
}, []);
return <div ref={el} />;
};
export const KOCommentEnd: React.FunctionComponent = () => {
const el = useRef();
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(el.current as any).outerHTML = `<!-- /ko -->`;
}, []);
return <div ref={el} />;
};

View File

@@ -16,15 +16,10 @@ test("Mongo CRUD", async () => {
// Create new database and collection
await explorer.click('[data-test="New Collection"]');
await explorer.click('[data-test="addCollection-newDatabaseId"]');
await explorer.fill('[data-test="addCollection-newDatabaseId"]', databaseId);
await explorer.click('[data-test="addCollection-collectionId"]');
await explorer.fill('[data-test="addCollection-collectionId"]', containerId);
await explorer.click('[data-test="addCollection-collectionId"]');
await explorer.fill('[data-test="addCollection-collectionId"]', containerId);
await explorer.click('[data-test="addCollection-partitionKeyValue"]');
await explorer.fill('[data-test="addCollection-partitionKeyValue"]', "/pk");
await explorer.click('[data-test="addCollection-createCollection"]');
await explorer.fill('[aria-label="New database id"]', databaseId);
await explorer.fill('[aria-label="Collection id"]', containerId);
await explorer.fill('[aria-label="Shard key"]', "/pk");
await explorer.click("#sidePanelOkButton");
await safeClick(explorer, `.nodeItem >> text=${databaseId}`);
await safeClick(explorer, `.nodeItem >> text=${containerId}`);
// Create indexing policy

View File

@@ -15,15 +15,10 @@ test("SQL CRUD", async () => {
});
await explorer.click('[data-test="New Container"]');
await explorer.click('[data-test="addCollection-newDatabaseId"]');
await explorer.fill('[data-test="addCollection-newDatabaseId"]', databaseId);
await explorer.click('[data-test="addCollection-collectionId"]');
await explorer.fill('[data-test="addCollection-collectionId"]', containerId);
await explorer.click('[data-test="addCollection-collectionId"]');
await explorer.fill('[data-test="addCollection-collectionId"]', containerId);
await explorer.click('[data-test="addCollection-partitionKeyValue"]');
await explorer.fill('[data-test="addCollection-partitionKeyValue"]', "/pk");
await explorer.click('[data-test="addCollection-createCollection"]');
await explorer.fill('[aria-label="New database id"]', databaseId);
await explorer.fill('[aria-label="Container id"]', containerId);
await explorer.fill('[aria-label="Partition key"]', "/pk");
await explorer.click("#sidePanelOkButton");
await safeClick(explorer, `.nodeItem >> text=${databaseId}`);
await safeClick(explorer, `[data-test="${containerId}"] [aria-label="More"]`);
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Container")');

View File

@@ -15,9 +15,8 @@ test("Tables CRUD", async () => {
});
await explorer.click('[data-test="New Table"]');
await explorer.click('[data-test="addCollection-collectionId"]');
await explorer.fill('[data-test="addCollection-collectionId"]', tableId);
await explorer.click('[data-test="addCollection-createCollection"]');
await explorer.fill('[aria-label="Table id"]', tableId);
await explorer.click("#sidePanelOkButton");
await safeClick(explorer, `[data-test="TablesDB"]`);
await safeClick(explorer, `[data-test="${tableId}"] [aria-label="More"]`);
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Table")');