diff --git a/.eslintignore b/.eslintignore index 967345584..f723666ed 100644 --- a/.eslintignore +++ b/.eslintignore @@ -111,13 +111,9 @@ src/Explorer/OpenActionsStubs.ts src/Explorer/Panes/AddDatabasePane.ts src/Explorer/Panes/AddDatabasePane.test.ts src/Explorer/Panes/BrowseQueriesPane.ts -src/Explorer/Panes/CassandraAddCollectionPane.ts src/Explorer/Panes/ContextualPaneBase.ts -src/Explorer/Panes/DeleteDatabaseConfirmationPane.test.ts -src/Explorer/Panes/DeleteDatabaseConfirmationPane.ts # src/Explorer/Panes/GraphStylingPane.ts # src/Explorer/Panes/NewVertexPane.ts -src/Explorer/Panes/PaneComponents.ts src/Explorer/Panes/RenewAdHocAccessPane.ts src/Explorer/Panes/SetupNotebooksPane.ts src/Explorer/Panes/SwitchDirectoryPane.ts diff --git a/src/Common/CosmosClient.ts b/src/Common/CosmosClient.ts index 9ba100b3b..756c53a2e 100644 --- a/src/Common/CosmosClient.ts +++ b/src/Common/CosmosClient.ts @@ -10,6 +10,13 @@ const _global = typeof self === "undefined" ? window : self; export const tokenProvider = async (requestInfo: RequestInfo) => { const { verb, resourceId, resourceType, headers } = requestInfo; + + if (userContext.features.enableAadDataPlane && userContext.aadToken) { + const AUTH_PREFIX = `type=aad&ver=1.0&sig=`; + const authorizationToken = `${AUTH_PREFIX}${userContext.aadToken}`; + return authorizationToken; + } + if (configContext.platform === Platform.Emulator) { // TODO This SDK method mutates the headers object. Find a better one or fix the SDK. await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey); @@ -76,7 +83,7 @@ export function client(): Cosmos.CosmosClient { if (_client) return _client; const options: Cosmos.CosmosClientOptions = { endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called - key: userContext.masterKey, + ...(!userContext.features.enableAadDataPlane && { key: userContext.masterKey }), tokenProvider, connectionPolicy: { enableEndpointDiscovery: false, diff --git a/src/Common/dataAccess/createDocument.ts b/src/Common/dataAccess/createDocument.ts index b64f70ff9..94dde951d 100644 --- a/src/Common/dataAccess/createDocument.ts +++ b/src/Common/dataAccess/createDocument.ts @@ -1,8 +1,8 @@ import { CollectionBase } from "../../Contracts/ViewModels"; +import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { client } from "../CosmosClient"; import { getEntityName } from "../DocumentUtility"; import { handleError } from "../ErrorHandlingUtils"; -import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; export const createDocument = async (collection: CollectionBase, newDocument: unknown): Promise => { const entityName = getEntityName(); diff --git a/src/Common/dataAccess/queryDocuments.ts b/src/Common/dataAccess/queryDocuments.ts index 16b2fb39e..c24e22ff6 100644 --- a/src/Common/dataAccess/queryDocuments.ts +++ b/src/Common/dataAccess/queryDocuments.ts @@ -1,6 +1,6 @@ -import { Queries } from "../Constants"; import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos"; import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility"; +import { Queries } from "../Constants"; import { client } from "../CosmosClient"; export const queryDocuments = ( diff --git a/src/Common/dataAccess/queryDocumentsPage.ts b/src/Common/dataAccess/queryDocumentsPage.ts index 064e4126f..e8b5447ef 100644 --- a/src/Common/dataAccess/queryDocumentsPage.ts +++ b/src/Common/dataAccess/queryDocumentsPage.ts @@ -1,8 +1,8 @@ import { QueryResults } from "../../Contracts/ViewModels"; import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; -import { MinimalQueryIterator, nextPage } from "../IteratorUtilities"; -import { handleError } from "../ErrorHandlingUtils"; import { getEntityName } from "../DocumentUtility"; +import { handleError } from "../ErrorHandlingUtils"; +import { MinimalQueryIterator, nextPage } from "../IteratorUtilities"; export const queryDocumentsPage = async ( resourceName: string, diff --git a/src/Contracts/DataModels.ts b/src/Contracts/DataModels.ts index aece6a04a..c017ec412 100644 --- a/src/Contracts/DataModels.ts +++ b/src/Contracts/DataModels.ts @@ -20,6 +20,8 @@ export interface DatabaseAccountExtendedProperties { writeLocations?: DatabaseAccountResponseLocation[]; enableFreeTier?: boolean; enableAnalyticalStorage?: boolean; + isVirtualNetworkFilterEnabled?: boolean; + ipRules?: IpRule[]; } export interface DatabaseAccountResponseLocation { @@ -31,6 +33,10 @@ export interface DatabaseAccountResponseLocation { provisioningState: string; } +export interface IpRule { + ipAddressOrRange: string; +} + export interface ConfigurationOverrides { EnableBsonSchema: string; } diff --git a/src/Contracts/ExplorerContracts.ts b/src/Contracts/ExplorerContracts.ts index 1689a96b5..d1c3dba58 100644 --- a/src/Contracts/ExplorerContracts.ts +++ b/src/Contracts/ExplorerContracts.ts @@ -1,6 +1,6 @@ -import * as Versions from "./Versions"; import * as ActionContracts from "./ActionContracts"; import * as Diagnostics from "./Diagnostics"; +import * as Versions from "./Versions"; /** * Messaging types used with Data Explorer <-> Portal communication diff --git a/src/Explorer/ComponentRegisterer.ts b/src/Explorer/ComponentRegisterer.ts index 9a49fd643..778e89f02 100644 --- a/src/Explorer/ComponentRegisterer.ts +++ b/src/Explorer/ComponentRegisterer.ts @@ -4,13 +4,9 @@ import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponen import { EditorComponent } from "./Controls/Editor/EditorComponent"; import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent"; import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3"; -import * as PaneComponents from "./Panes/PaneComponents"; ko.components.register("editor", new EditorComponent()); ko.components.register("json-editor", new JsonEditorComponent()); ko.components.register("diff-editor", new DiffEditorComponent()); ko.components.register("dynamic-list", DynamicListComponent); ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3); - -// Panes -ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent()); diff --git a/src/Explorer/ContextMenuButtonFactory.ts b/src/Explorer/ContextMenuButtonFactory.ts index 1940d025f..ed0ef9a59 100644 --- a/src/Explorer/ContextMenuButtonFactory.ts +++ b/src/Explorer/ContextMenuButtonFactory.ts @@ -73,9 +73,13 @@ export class ResourceTreeContextMenuButtonFactory { iconSrc: HostedTerminalIcon, onClick: () => { const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); - selectedCollection && selectedCollection.onNewMongoShellClick(); + if (container.isShellEnabled()) { + container.openNotebookTerminal(ViewModels.TerminalKind.Mongo); + } else { + selectedCollection && selectedCollection.onNewMongoShellClick(); + } }, - label: "New Shell", + label: container.isShellEnabled() ? "Open Mongo Shell" : "New Shell", }); } diff --git a/src/Explorer/Controls/NotebookGallery/CodeOfConductComponent/index.test.tsx b/src/Explorer/Controls/NotebookGallery/CodeOfConduct/CodeOfConduct.test.tsx similarity index 71% rename from src/Explorer/Controls/NotebookGallery/CodeOfConductComponent/index.test.tsx rename to src/Explorer/Controls/NotebookGallery/CodeOfConduct/CodeOfConduct.test.tsx index 79a6880a3..e99c0c8c2 100644 --- a/src/Explorer/Controls/NotebookGallery/CodeOfConductComponent/index.test.tsx +++ b/src/Explorer/Controls/NotebookGallery/CodeOfConduct/CodeOfConduct.test.tsx @@ -1,12 +1,12 @@ jest.mock("../../../../Juno/JunoClient"); import { shallow } from "enzyme"; import React from "react"; -import { CodeOfConductComponent, CodeOfConductComponentProps } from "."; import { HttpStatusCodes } from "../../../../Common/Constants"; import { JunoClient } from "../../../../Juno/JunoClient"; +import { CodeOfConduct, CodeOfConductProps } from "./CodeOfConduct"; -describe("CodeOfConductComponent", () => { - let codeOfConductProps: CodeOfConductComponentProps; +describe("CodeOfConduct", () => { + let codeOfConductProps: CodeOfConductProps; beforeEach(() => { const junoClient = new JunoClient(); @@ -21,12 +21,12 @@ describe("CodeOfConductComponent", () => { }); it("renders", () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); it("onAcceptedCodeOfConductCalled", async () => { - const wrapper = shallow(); + const wrapper = shallow(); wrapper.find(".genericPaneSubmitBtn").first().simulate("click"); await Promise.resolve(); expect(codeOfConductProps.onAcceptCodeOfConduct).toBeCalled(); diff --git a/src/Explorer/Controls/NotebookGallery/CodeOfConductComponent/index.tsx b/src/Explorer/Controls/NotebookGallery/CodeOfConduct/CodeOfConduct.tsx similarity index 92% rename from src/Explorer/Controls/NotebookGallery/CodeOfConductComponent/index.tsx rename to src/Explorer/Controls/NotebookGallery/CodeOfConduct/CodeOfConduct.tsx index 6d1d78fb2..2bfee23dc 100644 --- a/src/Explorer/Controls/NotebookGallery/CodeOfConductComponent/index.tsx +++ b/src/Explorer/Controls/NotebookGallery/CodeOfConduct/CodeOfConduct.tsx @@ -6,15 +6,15 @@ import { JunoClient } from "../../../../Juno/JunoClient"; import { Action } from "../../../../Shared/Telemetry/TelemetryConstants"; import { trace, traceFailure, traceStart, traceSuccess } from "../../../../Shared/Telemetry/TelemetryProcessor"; -export interface CodeOfConductComponentProps { +export interface CodeOfConductProps { junoClient: JunoClient; onAcceptCodeOfConduct: (result: boolean) => void; } -export const CodeOfConductComponent: FunctionComponent = ({ +export const CodeOfConduct: FunctionComponent = ({ junoClient, onAcceptCodeOfConduct, -}: CodeOfConductComponentProps) => { +}: CodeOfConductProps) => { const descriptionPara1 = "Azure Cosmos DB Notebook Gallery - Code of Conduct"; const descriptionPara2 = "The notebook public gallery contains notebook samples shared by users of Azure Cosmos DB."; const descriptionPara3 = "In order to view and publish your samples to the gallery, you must accept the "; @@ -47,7 +47,7 @@ export const CodeOfConductComponent: FunctionComponent void; -} - -interface CodeOfConductComponentState { - readCodeOfConduct: boolean; -} - -export class CodeOfConductComponent extends React.Component { - private viewCodeOfConductTraced: boolean; - private descriptionPara1: string; - private descriptionPara2: string; - private descriptionPara3: string; - private link1: { label: string; url: string }; - - constructor(props: CodeOfConductComponentProps) { - super(props); - - this.state = { - readCodeOfConduct: false, - }; - - this.descriptionPara1 = "Azure Cosmos DB Notebook Gallery - Code of Conduct"; - this.descriptionPara2 = "The notebook public gallery contains notebook samples shared by users of Azure Cosmos DB."; - this.descriptionPara3 = "In order to view and publish your samples to the gallery, you must accept the "; - this.link1 = { label: "code of conduct.", url: CodeOfConductEndpoints.codeOfConduct }; - } - - private async acceptCodeOfConduct(): Promise { - const startKey = traceStart(Action.NotebooksGalleryAcceptCodeOfConduct); - - try { - const response = await this.props.junoClient.acceptCodeOfConduct(); - if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) { - throw new Error(`Received HTTP ${response.status} when accepting code of conduct`); - } - - traceSuccess(Action.NotebooksGalleryAcceptCodeOfConduct, {}, startKey); - - this.props.onAcceptCodeOfConduct(response.data); - } catch (error) { - traceFailure( - Action.NotebooksGalleryAcceptCodeOfConduct, - { - error: getErrorMessage(error), - errorStack: getErrorStack(error), - }, - startKey - ); - - handleError(error, "CodeOfConductComponent/acceptCodeOfConduct", "Failed to accept code of conduct"); - } - } - - private onChangeCheckbox = (): void => { - this.setState({ readCodeOfConduct: !this.state.readCodeOfConduct }); - }; - - public render(): JSX.Element { - if (!this.viewCodeOfConductTraced) { - this.viewCodeOfConductTraced = true; - trace(Action.NotebooksGalleryViewCodeOfConduct); - } - - return ( - - - {this.descriptionPara1} - - - - {this.descriptionPara2} - - - - - {this.descriptionPara3} - - {this.link1.label} - - - - - - - - - - await this.acceptCodeOfConduct()} - tabIndex={0} - className="genericPaneSubmitBtn" - text="Continue" - disabled={!this.state.readCodeOfConduct} - /> - - - ); - } -} diff --git a/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.tsx b/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.tsx index f1733a141..e26ef58e0 100644 --- a/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.tsx +++ b/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.tsx @@ -30,7 +30,7 @@ import * as GalleryUtils from "../../../Utils/GalleryUtils"; import Explorer from "../../Explorer"; import { Dialog, DialogProps } from "../Dialog"; import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent"; -import { CodeOfConductComponent } from "./CodeOfConductComponent"; +import { CodeOfConduct } from "./CodeOfConduct/CodeOfConduct"; import "./GalleryViewerComponent.less"; import { InfoComponent } from "./InfoComponent/InfoComponent"; @@ -372,7 +372,7 @@ export class GalleryViewerComponent extends React.Component
- { this.setState({ isCodeOfConductAccepted: result }); diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap index adfb92b42..1fa13d27b 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap @@ -38,51 +38,6 @@ exports[`SettingsComponent renders 1`] = ` "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], - }, "closeDialog": undefined, "closeSidePanel": undefined, "collapsedResourceTreeWidth": 36, @@ -887,6 +842,7 @@ exports[`SettingsComponent renders 1`] = ` "isResourceTokenCollectionNodeSelected": [Function], "isSchemaEnabled": [Function], "isServerlessEnabled": [Function], + "isShellEnabled": [Function], "isSparkEnabled": [Function], "isSparkEnabledForAccount": [Function], "isSynapseLinkUpdating": [Function], @@ -978,51 +934,6 @@ exports[`SettingsComponent renders 1`] = ` "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], - }, "closeDialog": undefined, "closeSidePanel": undefined, "collapsedResourceTreeWidth": 36, @@ -1827,6 +1738,7 @@ exports[`SettingsComponent renders 1`] = ` "isResourceTokenCollectionNodeSelected": [Function], "isSchemaEnabled": [Function], "isServerlessEnabled": [Function], + "isShellEnabled": [Function], "isSparkEnabled": [Function], "isSparkEnabledForAccount": [Function], "isSynapseLinkUpdating": [Function], @@ -1931,51 +1843,6 @@ exports[`SettingsComponent renders 1`] = ` "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], - }, "closeDialog": undefined, "closeSidePanel": undefined, "collapsedResourceTreeWidth": 36, @@ -2780,6 +2647,7 @@ exports[`SettingsComponent renders 1`] = ` "isResourceTokenCollectionNodeSelected": [Function], "isSchemaEnabled": [Function], "isServerlessEnabled": [Function], + "isShellEnabled": [Function], "isSparkEnabled": [Function], "isSparkEnabledForAccount": [Function], "isSynapseLinkUpdating": [Function], @@ -2871,51 +2739,6 @@ exports[`SettingsComponent renders 1`] = ` "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], - }, "closeDialog": undefined, "closeSidePanel": undefined, "collapsedResourceTreeWidth": 36, @@ -3720,6 +3543,7 @@ exports[`SettingsComponent renders 1`] = ` "isResourceTokenCollectionNodeSelected": [Function], "isSchemaEnabled": [Function], "isServerlessEnabled": [Function], + "isShellEnabled": [Function], "isSparkEnabled": [Function], "isSparkEnabledForAccount": [Function], "isSynapseLinkUpdating": [Function], diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 85a377370..6793e1194 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -54,7 +54,7 @@ import { NotebookUtil } from "./Notebook/NotebookUtil"; import { AddCollectionPanel } from "./Panes/AddCollectionPanel"; import { AddDatabasePanel } from "./Panes/AddDatabasePanel/AddDatabasePanel"; import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane/BrowseQueriesPane"; -import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane"; +import { CassandraAddCollectionPane } from "./Panes/CassandraAddCollectionPane/CassandraAddCollectionPane"; import { ContextualPaneBase } from "./Panes/ContextualPaneBase"; import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane"; import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel"; @@ -67,7 +67,7 @@ import { SetupNoteBooksPanel } from "./Panes/SetupNotebooksPanel/SetupNotebooksP import { StringInputPane } from "./Panes/StringInputPane/StringInputPane"; import { AddTableEntityPanel } from "./Panes/Tables/AddTableEntityPanel"; import { EditTableEntityPanel } from "./Panes/Tables/EditTableEntityPanel"; -import { TableQuerySelectPanel } from "./Panes/Tables/TableQuerySelectPanel"; +import { TableQuerySelectPanel } from "./Panes/Tables/TableQuerySelectPanel/TableQuerySelectPanel"; import { UploadFilePane } from "./Panes/UploadFilePane/UploadFilePane"; import { UploadItemsPane } from "./Panes/UploadItemsPane/UploadItemsPane"; import TableListViewModal from "./Tables/DataTable/TableEntityListViewModel"; @@ -148,7 +148,6 @@ export default class Explorer { public tabsManager: TabsManager; // Contextual panes - public cassandraAddCollectionPane: CassandraAddCollectionPane; private gitHubClient: GitHubClient; public gitHubOAuthService: GitHubOAuthService; public junoClient: JunoClient; @@ -178,6 +177,8 @@ export default class Explorer { public openDialog: ExplorerParams["openDialog"]; public closeDialog: ExplorerParams["closeDialog"]; + public isShellEnabled: ko.Observable; + private _isInitializingNotebooks: boolean; private notebookBasePath: ko.Observable; private _arcadiaManager: ArcadiaResourceManager; @@ -223,6 +224,7 @@ export default class Explorer { }); } }); + this.isShellEnabled = ko.observable(false); this.isNotebooksEnabledForAccount = ko.observable(false); this.isNotebooksEnabledForAccount.subscribe((isEnabledForAccount: boolean) => this.refreshCommandBarButtons()); this.isSparkEnabledForAccount = ko.observable(false); @@ -249,6 +251,12 @@ export default class Explorer { ((await this._containsDefaultNotebookWorkspace(userContext.databaseAccount)) || userContext.features.enableNotebooks) ); + this.isShellEnabled( + this.isNotebookEnabled() && + !userContext.databaseAccount.properties.isVirtualNetworkFilterEnabled && + userContext.databaseAccount.properties.ipRules.length === 0 + ); + TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, { isNotebookEnabled: this.isNotebookEnabled(), dataExplorerArea: Constants.Areas.Notebook, @@ -398,13 +406,6 @@ export default class Explorer { } }); - this.cassandraAddCollectionPane = new CassandraAddCollectionPane({ - id: "cassandraaddcollectionpane", - visible: ko.observable(false), - - container: this, - }); - this.tabsManager = params?.tabsManager ?? new TabsManager(); this.tabsManager.openedTabs.subscribe((tabs) => { if (tabs.length === 0) { @@ -1138,7 +1139,10 @@ export default class Explorer { private getDeltaDatabases( updatedDatabaseList: DataModels.Database[] - ): { toAdd: ViewModels.Database[]; toDelete: ViewModels.Database[] } { + ): { + toAdd: ViewModels.Database[]; + toDelete: ViewModels.Database[]; + } { const newDatabases: DataModels.Database[] = _.filter(updatedDatabaseList, (database: DataModels.Database) => { const databaseExists = _.some( this.databases(), @@ -1384,7 +1388,7 @@ export default class Explorer { this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again."); } else { this.openSidePanel( - "", + "Rename Notebook", { @@ -1415,7 +1419,7 @@ export default class Explorer { } this.openSidePanel( - "", + "Create new directory", { @@ -1720,32 +1724,27 @@ export default class Explorer { throw new Error("Terminal kind: ${kind} not supported"); } - const terminalTabs: TerminalTab[] = this.tabsManager.getTabs( - ViewModels.CollectionTabKind.Terminal, - (tab) => tab.hashLocation() == hashLocation + const terminalTabs: TerminalTab[] = this.tabsManager.getTabs(ViewModels.CollectionTabKind.Terminal, (tab) => + tab.hashLocation().startsWith(hashLocation) ) as TerminalTab[]; - let terminalTab: TerminalTab = terminalTabs && terminalTabs[0]; - if (terminalTab) { - this.tabsManager.activateTab(terminalTab); - } else { - const newTab = new TerminalTab({ - account: userContext.databaseAccount, - tabKind: ViewModels.CollectionTabKind.Terminal, - node: null, - title: title, - tabPath: title, - collection: null, - hashLocation: hashLocation, - isTabsContentExpanded: ko.observable(true), - onLoadStartKey: null, - onUpdateTabsButtons: this.onUpdateTabsButtons, - container: this, - kind: kind, - }); + const index = terminalTabs.length + 1; + const newTab = new TerminalTab({ + account: userContext.databaseAccount, + tabKind: ViewModels.CollectionTabKind.Terminal, + node: null, + title: `${title} ${index}`, + tabPath: `${title} ${index}`, + collection: null, + hashLocation: `${hashLocation} ${index}`, + isTabsContentExpanded: ko.observable(true), + onLoadStartKey: null, + onUpdateTabsButtons: this.onUpdateTabsButtons, + container: this, + kind: kind, + }); - this.tabsManager.activateNewTab(newTab); - } + this.tabsManager.activateNewTab(newTab); } public async openGallery( @@ -1791,7 +1790,7 @@ export default class Explorer { public onNewCollectionClicked(databaseId?: string): void { if (userContext.apiType === "Cassandra") { - this.cassandraAddCollectionPane.open(); + this.openCassandraAddCollectionPane(); } else { this.openAddCollectionPanel(databaseId); } @@ -1907,7 +1906,7 @@ export default class Explorer { "Delete " + getDatabaseName(), this.expandConsole()} closePanel={this.closeSidePanel} selectedDatabase={this.findSelectedDatabase()} /> @@ -1983,6 +1982,16 @@ export default class Explorer { ); } + public openCassandraAddCollectionPane(): void { + this.openSidePanel( + "Add Table", + this.closeSidePanel()} + cassandraApiClient={new CassandraAPIDataClient()} + /> + ); + } public openGitHubReposPanel(header: string, junoClient?: JunoClient): void { this.openSidePanel( header, diff --git a/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.tsx b/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.tsx index d3011155a..fce3d43e7 100644 --- a/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.tsx +++ b/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.tsx @@ -132,6 +132,7 @@ export const NewVertexComponent: FunctionComponent = ( onChange={(event: React.ChangeEvent) => { onLabelChange(event); }} + autoFocus />
diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts index 268f149b7..63172c8d5 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts @@ -139,6 +139,7 @@ describe("CommandBarComponentButtonFactory tests", () => { mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isServerlessEnabled = ko.computed(() => false); + mockExplorer.isShellEnabled = ko.observable(true); }); afterAll(() => { @@ -154,6 +155,7 @@ describe("CommandBarComponentButtonFactory tests", () => { mockExplorer.isNotebookEnabled = ko.observable(false); mockExplorer.isNotebooksEnabledForAccount = ko.observable(false); mockExplorer.isRunningOnNationalCloud = ko.observable(false); + mockExplorer.isShellEnabled = ko.observable(true); }); it("Mongo Api not available - button should be hidden", () => { @@ -173,24 +175,18 @@ describe("CommandBarComponentButtonFactory tests", () => { expect(openMongoShellBtn).toBeUndefined(); }); - it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => { + it("Notebooks is not enabled and is unavailable - button should be hidden", () => { const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeDefined(); - expect(openMongoShellBtn.disabled).toBe(true); - expect(openMongoShellBtn.tooltipText).toBe( - "This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks." - ); + expect(openMongoShellBtn).toBeUndefined(); }); - it("Notebooks is not enabled and is available - button should be shown and enabled", () => { + it("Notebooks is not enabled and is available - button should be hidden", () => { mockExplorer.isNotebooksEnabledForAccount = ko.observable(true); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeDefined(); - expect(openMongoShellBtn.disabled).toBe(false); - expect(openMongoShellBtn.tooltipText).toBe(""); + expect(openMongoShellBtn).toBeUndefined(); }); it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => { @@ -213,6 +209,16 @@ describe("CommandBarComponentButtonFactory tests", () => { expect(openMongoShellBtn.disabled).toBe(false); expect(openMongoShellBtn.tooltipText).toBe(""); }); + + it("Notebooks is enabled and is available, terminal is unavailable due to ipRules - button should be hidden", () => { + mockExplorer.isNotebookEnabled = ko.observable(true); + mockExplorer.isNotebooksEnabledForAccount = ko.observable(true); + mockExplorer.isShellEnabled = ko.observable(false); + + const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); + const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); + expect(openMongoShellBtn).toBeUndefined(); + }); }); describe("Open Cassandra Shell button", () => { @@ -273,11 +279,7 @@ describe("CommandBarComponentButtonFactory tests", () => { it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => { const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); - expect(openCassandraShellBtn).toBeDefined(); - expect(openCassandraShellBtn.disabled).toBe(true); - expect(openCassandraShellBtn.tooltipText).toBe( - "This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks." - ); + expect(openCassandraShellBtn).toBeUndefined(); }); it("Notebooks is not enabled and is available - button should be shown and enabled", () => { @@ -285,9 +287,7 @@ describe("CommandBarComponentButtonFactory tests", () => { const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); - expect(openCassandraShellBtn).toBeDefined(); - expect(openCassandraShellBtn.disabled).toBe(false); - expect(openCassandraShellBtn.tooltipText).toBe(""); + expect(openCassandraShellBtn).toBeUndefined(); }); it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => { diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index da8b4251d..41c1bb9e9 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -61,48 +61,40 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto if (container.notebookManager?.gitHubOAuthService) { buttons.push(createManageGitHubAccountButton(container)); } - } - if (!container.isRunningOnNationalCloud()) { - if (!container.isNotebookEnabled()) { - buttons.push(createEnableNotebooksButton(container)); - } - - if (userContext.apiType === "Mongo") { - buttons.push(createOpenMongoTerminalButton(container)); - } - - if (userContext.apiType === "Cassandra") { - buttons.push(createOpenCassandraTerminalButton(container)); - } - } - - if (container.isNotebookEnabled()) { buttons.push(createOpenTerminalButton(container)); buttons.push(createNotebookWorkspaceResetButton(container)); + if ( + (userContext.apiType === "Mongo" && container.isShellEnabled() && container.isDatabaseNodeOrNoneSelected()) || + userContext.apiType === "Cassandra" + ) { + buttons.push(createDivider()); + if (userContext.apiType === "Cassandra") { + buttons.push(createOpenCassandraTerminalButton(container)); + } else { + buttons.push(createOpenMongoTerminalButton(container)); + } + } + } else { + if (!container.isRunningOnNationalCloud()) { + buttons.push(createEnableNotebooksButton(container)); + } } if (!container.isDatabaseNodeOrNoneSelected()) { - if (container.isNotebookEnabled()) { - buttons.push(createDivider()); - } + const isQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin"; - const isSqlQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin"; - if (isSqlQuerySupported) { + if (isQuerySupported) { + buttons.push(createDivider()); const newSqlQueryBtn = createNewSQLQueryButton(container); buttons.push(newSqlQueryBtn); } - const isSupportedOpenQueryApi = - userContext.apiType === "SQL" || userContext.apiType === "Mongo" || userContext.apiType === "Gremlin"; - const isSupportedOpenQueryFromDiskApi = userContext.apiType === "SQL" || userContext.apiType === "Gremlin"; - if (isSupportedOpenQueryApi && container.selectedNode() && container.findSelectedCollection()) { + if (isQuerySupported && container.selectedNode() && container.findSelectedCollection()) { const openQueryBtn = createOpenQueryButton(container); openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton(container)]; buttons.push(openQueryBtn); - } else if (isSupportedOpenQueryFromDiskApi && container.selectedNode() && container.findSelectedCollection()) { - buttons.push(createOpenQueryFromDiskButton(container)); } if (areScriptsSupported()) { @@ -132,13 +124,17 @@ export function createContextCommandBarButtons(container: Explorer): CommandButt const buttons: CommandButtonComponentProps[] = []; if (!container.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") { - const label = "New Shell"; + const label = container.isShellEnabled() ? "Open Mongo Shell" : "New Shell"; const newMongoShellBtn: CommandButtonComponentProps = { iconSrc: HostedTerminalIcon, iconAlt: label, onCommandClick: () => { const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); - selectedCollection && selectedCollection.onNewMongoShellClick(); + if (container.isShellEnabled()) { + container.openNotebookTerminal(ViewModels.TerminalKind.Mongo); + } else { + selectedCollection && selectedCollection.onNewMongoShellClick(); + } }, commandButtonLabel: label, ariaLabel: label, diff --git a/src/Explorer/OpenActions.test.ts b/src/Explorer/OpenActions.test.ts index 0ea2057b7..d06e8b239 100644 --- a/src/Explorer/OpenActions.test.ts +++ b/src/Explorer/OpenActions.test.ts @@ -3,7 +3,6 @@ import { ActionContracts } from "../Contracts/ExplorerContracts"; import * as ViewModels from "../Contracts/ViewModels"; import Explorer from "./Explorer"; import { handleOpenAction } from "./OpenActions"; -import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane"; describe("OpenActions", () => { describe("handleOpenAction", () => { @@ -15,8 +14,6 @@ describe("OpenActions", () => { beforeEach(() => { explorer = {} as Explorer; explorer.onNewCollectionClicked = jest.fn(); - explorer.cassandraAddCollectionPane = {} as CassandraAddCollectionPane; - explorer.cassandraAddCollectionPane.open = jest.fn(); database = { id: ko.observable("db"), @@ -64,28 +61,6 @@ describe("OpenActions", () => { expect(actionHandled).toBe(true); }); - describe("CassandraAddCollection pane kind", () => { - it("string value should call cassandraAddCollectionPane.open", () => { - const action = { - actionType: "OpenPane", - paneKind: "CassandraAddCollection", - }; - - const actionHandled = handleOpenAction(action, [], explorer); - expect(explorer.cassandraAddCollectionPane.open).toHaveBeenCalled(); - }); - - it("enum value should call cassandraAddCollectionPane.open", () => { - const action = { - actionType: "OpenPane", - paneKind: ActionContracts.PaneKind.CassandraAddCollection, - }; - - const actionHandled = handleOpenAction(action, [], explorer); - expect(explorer.cassandraAddCollectionPane.open).toHaveBeenCalled(); - }); - }); - describe("AddCollection pane kind", () => { it("string value should call explorer.onNewCollectionClicked", () => { const action = { diff --git a/src/Explorer/OpenActions.ts b/src/Explorer/OpenActions.ts index afc3251c2..033cb8135 100644 --- a/src/Explorer/OpenActions.ts +++ b/src/Explorer/OpenActions.ts @@ -145,7 +145,7 @@ function openPane(action: ActionContracts.OpenPane, explorer: Explorer) { action.paneKind === ActionContracts.PaneKind.CassandraAddCollection || (action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.CassandraAddCollection] ) { - explorer.cassandraAddCollectionPane.open(); + explorer.openCassandraAddCollectionPane(); } else if ( action.paneKind === ActionContracts.PaneKind.GlobalSettings || (action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.GlobalSettings] diff --git a/src/Explorer/Panes/CassandraAddCollectionPane.html b/src/Explorer/Panes/CassandraAddCollectionPane.html deleted file mode 100644 index 2450196ee..000000000 --- a/src/Explorer/Panes/CassandraAddCollectionPane.html +++ /dev/null @@ -1,273 +0,0 @@ -
-
-
- -
- - - -
- loading indicator -
- -
-
-
diff --git a/src/Explorer/Panes/CassandraAddCollectionPane.ts b/src/Explorer/Panes/CassandraAddCollectionPane.ts deleted file mode 100644 index 18879e6d3..000000000 --- a/src/Explorer/Panes/CassandraAddCollectionPane.ts +++ /dev/null @@ -1,539 +0,0 @@ -import * as ko from "knockout"; -import * as _ from "underscore"; -import * as Constants from "../../Common/Constants"; -import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; -import { configContext, Platform } from "../../ConfigContext"; -import * as DataModels from "../../Contracts/DataModels"; -import * as ViewModels from "../../Contracts/ViewModels"; -import * as AddCollectionUtility from "../../Shared/AddCollectionUtility"; -import * as SharedConstants from "../../Shared/Constants"; -import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; -import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; -import { userContext } from "../../UserContext"; -import * as AutoPilotUtils from "../../Utils/AutoPilotUtils"; -import * as PricingUtils from "../../Utils/PricingUtils"; -import { CassandraAPIDataClient } from "../Tables/TableDataClient"; -import { ContextualPaneBase } from "./ContextualPaneBase"; - -export default class CassandraAddCollectionPane extends ContextualPaneBase { - public createTableQuery: ko.Observable; - public keyspaceId: ko.Observable; - public maxThroughputRU: ko.Observable; - public minThroughputRU: ko.Observable; - public tableId: ko.Observable; - public throughput: ko.Observable; - public throughputRangeText: ko.Computed; - public sharedThroughputRangeText: ko.Computed; - public userTableQuery: ko.Observable; - public requestUnitsUsageCostDedicated: ko.Computed; - public requestUnitsUsageCostShared: ko.Computed; - public costsVisible: ko.PureComputed; - public keyspaceHasSharedOffer: ko.Observable; - public keyspaceIds: ko.ObservableArray; - public keyspaceThroughput: ko.Observable; - public keyspaceCreateNew: ko.Observable; - public dedicateTableThroughput: ko.Observable; - public canRequestSupport: ko.PureComputed; - public throughputSpendAckText: ko.Observable; - public throughputSpendAck: ko.Observable; - public sharedThroughputSpendAck: ko.Observable; - public sharedThroughputSpendAckText: ko.Observable; - public isAutoPilotSelected: ko.Observable; - public isSharedAutoPilotSelected: ko.Observable; - public selectedAutoPilotThroughput: ko.Observable; - public sharedAutoPilotThroughput: ko.Observable; - public autoPilotUsageCost: ko.Computed; - public sharedThroughputSpendAckVisible: ko.Computed; - public throughputSpendAckVisible: ko.Computed; - public canExceedMaximumValue: ko.PureComputed; - public isFreeTierAccount: ko.Computed; - public ruToolTipText: ko.Computed; - public canConfigureThroughput: ko.PureComputed; - - private keyspaceOffers: Map; - - constructor(options: ViewModels.PaneOptions) { - super(options); - this.title("Add Table"); - this.createTableQuery = ko.observable("CREATE TABLE "); - this.keyspaceCreateNew = ko.observable(true); - this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText()); - this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled()); - this.keyspaceOffers = new Map(); - this.keyspaceIds = ko.observableArray(); - this.keyspaceHasSharedOffer = ko.observable(false); - this.keyspaceThroughput = ko.observable(); - this.keyspaceId = ko.observable(""); - this.keyspaceId.subscribe((keyspaceId: string) => { - if (this.keyspaceIds.indexOf(keyspaceId) >= 0) { - this.keyspaceHasSharedOffer(this.keyspaceOffers.has(keyspaceId)); - } - }); - this.keyspaceId.extend({ rateLimit: 100 }); - this.dedicateTableThroughput = ko.observable(false); - const throughputDefaults = this.container.collectionCreationDefaults.throughput; - this.maxThroughputRU = ko.observable(throughputDefaults.unlimitedmax); - this.minThroughputRU = ko.observable(throughputDefaults.unlimitedmin); - - this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue()); - - this.isFreeTierAccount = ko.computed(() => { - return userContext?.databaseAccount?.properties?.enableFreeTier; - }); - - this.tableId = ko.observable(""); - this.isAutoPilotSelected = ko.observable(false); - this.isSharedAutoPilotSelected = ko.observable(false); - this.selectedAutoPilotThroughput = ko.observable(); - this.sharedAutoPilotThroughput = ko.observable(); - this.throughput = ko.observable(); - this.throughputRangeText = ko.pureComputed(() => { - const enableAutoPilot = this.isAutoPilotSelected(); - if (!enableAutoPilot) { - return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`; - } - return AutoPilotUtils.getAutoPilotHeaderText(); - }); - this.sharedThroughputRangeText = ko.pureComputed(() => { - if (this.isSharedAutoPilotSelected()) { - return AutoPilotUtils.getAutoPilotHeaderText(); - } - return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`; - }); - this.userTableQuery = ko.observable("(userid int, name text, email text, PRIMARY KEY (userid))"); - this.keyspaceId.subscribe((keyspaceId) => { - this.createTableQuery(`CREATE TABLE ${keyspaceId}.`); - }); - - this.throughputSpendAckText = ko.observable(); - this.throughputSpendAck = ko.observable(false); - this.sharedThroughputSpendAck = ko.observable(false); - this.sharedThroughputSpendAckText = ko.observable(); - - this.resetData(); - - this.requestUnitsUsageCostDedicated = ko.computed(() => { - const { databaseAccount: account } = userContext; - if (!account) { - return ""; - } - - const regions = - (account && - account.properties && - account.properties.readLocations && - account.properties.readLocations.length) || - 1; - const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false; - const offerThroughput: number = this.throughput(); - let estimatedSpend: string; - let estimatedDedicatedSpendAcknowledge: string; - if (!this.isAutoPilotSelected()) { - estimatedSpend = PricingUtils.getEstimatedSpendHtml( - offerThroughput, - userContext.portalEnv, - regions, - multimaster - ); - estimatedDedicatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString( - offerThroughput, - userContext.portalEnv, - regions, - multimaster, - this.isAutoPilotSelected() - ); - } else { - estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml( - this.selectedAutoPilotThroughput(), - userContext.portalEnv, - regions, - multimaster - ); - estimatedDedicatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString( - this.selectedAutoPilotThroughput(), - userContext.portalEnv, - regions, - multimaster, - this.isAutoPilotSelected() - ); - } - this.throughputSpendAckText(estimatedDedicatedSpendAcknowledge); - return estimatedSpend; - }); - - this.requestUnitsUsageCostShared = ko.computed(() => { - const { databaseAccount: account } = userContext; - if (!account) { - return ""; - } - - const regions = account?.properties?.readLocations?.length || 1; - const multimaster = account?.properties?.enableMultipleWriteLocations || false; - let estimatedSpend: string; - let estimatedSharedSpendAcknowledge: string; - if (!this.isSharedAutoPilotSelected()) { - estimatedSpend = PricingUtils.getEstimatedSpendHtml( - this.keyspaceThroughput(), - userContext.portalEnv, - regions, - multimaster - ); - estimatedSharedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString( - this.keyspaceThroughput(), - userContext.portalEnv, - regions, - multimaster, - this.isSharedAutoPilotSelected() - ); - } else { - estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml( - this.sharedAutoPilotThroughput(), - userContext.portalEnv, - regions, - multimaster - ); - estimatedSharedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString( - this.sharedAutoPilotThroughput(), - userContext.portalEnv, - regions, - multimaster, - this.isSharedAutoPilotSelected() - ); - } - this.sharedThroughputSpendAckText(estimatedSharedSpendAcknowledge); - return estimatedSpend; - }); - - this.costsVisible = ko.pureComputed(() => { - return configContext.platform !== Platform.Emulator; - }); - - this.canRequestSupport = ko.pureComputed(() => { - if (configContext.platform !== Platform.Emulator && !userContext.isTryCosmosDBSubscription) { - const offerThroughput: number = this.throughput(); - return offerThroughput <= 100000; - } - - return false; - }); - - this.sharedThroughputSpendAckVisible = ko.computed(() => { - const autoscaleThroughput = this.sharedAutoPilotThroughput() * 1; - if (this.isSharedAutoPilotSelected()) { - return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; - } - - return this.keyspaceThroughput() > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; - }); - - this.throughputSpendAckVisible = ko.pureComputed(() => { - const autoscaleThroughput = this.selectedAutoPilotThroughput() * 1; - if (this.isAutoPilotSelected()) { - return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; - } - - return this.throughput() > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; - }); - - if (!!this.container) { - const updateKeyspaceIds: (keyspaces: ViewModels.Database[]) => void = ( - newKeyspaceIds: ViewModels.Database[] - ): void => { - const cachedKeyspaceIdsList = _.map(newKeyspaceIds, (keyspace: ViewModels.Database) => { - if (keyspace && keyspace.offer && !!keyspace.offer()) { - this.keyspaceOffers.set(keyspace.id(), keyspace.offer()); - } - return keyspace.id(); - }); - this.keyspaceIds(cachedKeyspaceIdsList); - }; - this.container.databases.subscribe((newDatabases: ViewModels.Database[]) => updateKeyspaceIds(newDatabases)); - updateKeyspaceIds(this.container.databases()); - } - - this.autoPilotUsageCost = ko.pureComputed(() => { - const autoPilot = this._getAutoPilot(); - if (!autoPilot) { - return ""; - } - const isDatabaseThroughput: boolean = this.keyspaceCreateNew(); - return PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, isDatabaseThroughput); - }); - } - - public decreaseThroughput() { - let offerThroughput: number = this.throughput(); - - if (offerThroughput > this.minThroughputRU()) { - offerThroughput -= 100; - this.throughput(offerThroughput); - } - } - - public increaseThroughput() { - let offerThroughput: number = this.throughput(); - - if (offerThroughput < this.maxThroughputRU()) { - offerThroughput += 100; - this.throughput(offerThroughput); - } - } - - public open() { - super.open(); - if (!this.container.isServerlessEnabled()) { - this.isAutoPilotSelected(this.container.isAutoscaleDefaultEnabled()); - } - const addCollectionPaneOpenMessage = { - collection: ko.toJS({ - id: this.tableId(), - storage: Constants.BackendDefaults.multiPartitionStorageInGb, - offerThroughput: this.throughput(), - partitionKey: "", - databaseId: this.keyspaceId(), - }), - subscriptionType: userContext.subscriptionType, - subscriptionQuotaId: userContext.quotaId, - defaultsCheck: { - storage: "u", - throughput: this.throughput(), - flight: userContext.addCollectionFlight, - }, - dataExplorerArea: Constants.Areas.ContextualPane, - }; - const focusElement = document.getElementById("keyspace-id"); - focusElement && focusElement.focus(); - TelemetryProcessor.trace(Action.CreateCollection, ActionModifiers.Open, addCollectionPaneOpenMessage); - } - - public submit() { - if (!this._isValid()) { - return; - } - this.isExecuting(true); - const autoPilotCommand = `cosmosdb_autoscale_max_throughput`; - let createTableAndKeyspacePromise: Q.Promise; - const toCreateKeyspace: boolean = this.keyspaceCreateNew(); - const useAutoPilotForKeyspace: boolean = this.isSharedAutoPilotSelected() && !!this.sharedAutoPilotThroughput(); - const createKeyspaceQueryPrefix: string = `CREATE KEYSPACE ${this.keyspaceId().trim()} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 }`; - const createKeyspaceQuery: string = this.keyspaceHasSharedOffer() - ? useAutoPilotForKeyspace - ? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${this.sharedAutoPilotThroughput()};` - : `${createKeyspaceQueryPrefix} AND cosmosdb_provisioned_throughput=${this.keyspaceThroughput()};` - : `${createKeyspaceQueryPrefix};`; - const createTableQueryPrefix: string = `${this.createTableQuery()}${this.tableId().trim()} ${this.userTableQuery()}`; - let createTableQuery: string; - - if (this.canConfigureThroughput() && (this.dedicateTableThroughput() || !this.keyspaceHasSharedOffer())) { - if (this.isAutoPilotSelected() && this.selectedAutoPilotThroughput()) { - createTableQuery = `${createTableQueryPrefix} WITH ${autoPilotCommand}=${this.selectedAutoPilotThroughput()};`; - } else { - createTableQuery = `${createTableQueryPrefix} WITH cosmosdb_provisioned_throughput=${this.throughput()};`; - } - } else { - createTableQuery = `${createTableQueryPrefix};`; - } - - const addCollectionPaneStartMessage = { - collection: ko.toJS({ - id: this.tableId(), - storage: Constants.BackendDefaults.multiPartitionStorageInGb, - offerThroughput: this.throughput(), - partitionKey: "", - databaseId: this.keyspaceId(), - hasDedicatedThroughput: this.dedicateTableThroughput(), - }), - keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(), - subscriptionType: userContext.subscriptionType, - subscriptionQuotaId: userContext.quotaId, - defaultsCheck: { - storage: "u", - throughput: this.throughput(), - flight: userContext.addCollectionFlight, - }, - dataExplorerArea: Constants.Areas.ContextualPane, - toCreateKeyspace: toCreateKeyspace, - createKeyspaceQuery: createKeyspaceQuery, - createTableQuery: createTableQuery, - }; - const startKey: number = TelemetryProcessor.traceStart(Action.CreateCollection, addCollectionPaneStartMessage); - const { databaseAccount } = userContext; - if (toCreateKeyspace) { - createTableAndKeyspacePromise = (this.container.tableDataClient).createTableAndKeyspace( - databaseAccount?.properties.cassandraEndpoint, - databaseAccount?.id, - this.container, - createTableQuery, - createKeyspaceQuery - ); - } else { - createTableAndKeyspacePromise = (this.container.tableDataClient).createTableAndKeyspace( - databaseAccount?.properties.cassandraEndpoint, - databaseAccount?.id, - this.container, - createTableQuery - ); - } - createTableAndKeyspacePromise.then( - () => { - this.container.refreshAllDatabases(); - this.isExecuting(false); - this.close(); - const addCollectionPaneSuccessMessage = { - collection: ko.toJS({ - id: this.tableId(), - storage: Constants.BackendDefaults.multiPartitionStorageInGb, - offerThroughput: this.throughput(), - partitionKey: "", - databaseId: this.keyspaceId(), - hasDedicatedThroughput: this.dedicateTableThroughput(), - }), - keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(), - subscriptionType: userContext.subscriptionType, - subscriptionQuotaId: userContext.quotaId, - defaultsCheck: { - storage: "u", - throughput: this.throughput(), - flight: userContext.addCollectionFlight, - }, - dataExplorerArea: Constants.Areas.ContextualPane, - toCreateKeyspace: toCreateKeyspace, - createKeyspaceQuery: createKeyspaceQuery, - createTableQuery: createTableQuery, - }; - TelemetryProcessor.traceSuccess(Action.CreateCollection, addCollectionPaneSuccessMessage, startKey); - }, - (error) => { - const errorMessage = getErrorMessage(error); - this.formErrors(errorMessage); - this.isExecuting(false); - const addCollectionPaneFailedMessage = { - collection: { - id: this.tableId(), - storage: Constants.BackendDefaults.multiPartitionStorageInGb, - offerThroughput: this.throughput(), - partitionKey: "", - databaseId: this.keyspaceId(), - hasDedicatedThroughput: this.dedicateTableThroughput(), - }, - keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(), - subscriptionType: userContext.subscriptionType, - subscriptionQuotaId: userContext.quotaId, - defaultsCheck: { - storage: "u", - throughput: this.throughput(), - flight: userContext.addCollectionFlight, - }, - dataExplorerArea: Constants.Areas.ContextualPane, - toCreateKeyspace: toCreateKeyspace, - createKeyspaceQuery: createKeyspaceQuery, - createTableQuery: createTableQuery, - error: errorMessage, - errorStack: getErrorStack(error), - }; - TelemetryProcessor.traceFailure(Action.CreateCollection, addCollectionPaneFailedMessage, startKey); - } - ); - } - - public resetData() { - super.resetData(); - const throughputDefaults = this.container.collectionCreationDefaults.throughput; - this.isAutoPilotSelected(this.container.isAutoscaleDefaultEnabled()); - this.isSharedAutoPilotSelected(this.container.isAutoscaleDefaultEnabled()); - this.selectedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput); - this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput); - this.throughput(AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container)); - this.keyspaceThroughput(throughputDefaults.shared); - this.maxThroughputRU(throughputDefaults.unlimitedmax); - this.minThroughputRU(throughputDefaults.unlimitedmin); - this.createTableQuery("CREATE TABLE "); - this.userTableQuery("(userid int, name text, email text, PRIMARY KEY (userid))"); - this.tableId(""); - this.keyspaceId(""); - this.throughputSpendAck(false); - this.keyspaceHasSharedOffer(false); - this.keyspaceCreateNew(true); - } - - private _isValid(): boolean { - const throughput = this.throughput(); - const keyspaceThroughput = this.keyspaceThroughput(); - - const sharedAutoscaleThroughput = this.sharedAutoPilotThroughput() * 1; - if ( - this.isSharedAutoPilotSelected() && - sharedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && - !this.sharedThroughputSpendAck() - ) { - this.formErrors(`Please acknowledge the estimated monthly spend.`); - return false; - } - - const dedicatedAutoscaleThroughput = this.selectedAutoPilotThroughput() * 1; - if ( - this.isAutoPilotSelected() && - dedicatedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && - !this.throughputSpendAck() - ) { - this.formErrors(`Please acknowledge the estimated monthly spend.`); - return false; - } - - if ( - (this.keyspaceCreateNew() && this.keyspaceHasSharedOffer() && this.isSharedAutoPilotSelected()) || - this.isAutoPilotSelected() - ) { - const autoPilot = this._getAutoPilot(); - if ( - !autoPilot || - !autoPilot.maxThroughput || - !AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput) - ) { - this.formErrors( - `Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput` - ); - return false; - } - return true; - } - - if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !this.throughputSpendAck()) { - this.formErrors(`Please acknowledge the estimated daily spend.`); - return false; - } - - if ( - this.keyspaceHasSharedOffer() && - this.keyspaceCreateNew() && - keyspaceThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && - !this.sharedThroughputSpendAck() - ) { - this.formErrors("Please acknowledge the estimated daily spend"); - return false; - } - - return true; - } - - private _getAutoPilot(): DataModels.AutoPilotCreationSettings { - if ( - this.keyspaceCreateNew() && - this.keyspaceHasSharedOffer() && - this.isSharedAutoPilotSelected() && - this.sharedAutoPilotThroughput() - ) { - return { - maxThroughput: this.sharedAutoPilotThroughput() * 1, - }; - } - - if (this.selectedAutoPilotThroughput()) { - return { - maxThroughput: this.selectedAutoPilotThroughput() * 1, - }; - } - - return undefined; - } -} diff --git a/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.test.tsx b/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.test.tsx new file mode 100644 index 000000000..20433bac5 --- /dev/null +++ b/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.test.tsx @@ -0,0 +1,32 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import { shallow } from "enzyme"; +import React from "react"; +import Explorer from "../../Explorer"; +import { CassandraAPIDataClient } from "../../Tables/TableDataClient"; +import { CassandraAddCollectionPane } from "./CassandraAddCollectionPane"; +const props = { + explorer: new Explorer(), + closePanel: (): void => undefined, + cassandraApiClient: new CassandraAPIDataClient(), +}; + +describe("CassandraAddCollectionPane Pane", () => { + beforeEach(() => render()); + + it("should render Default properly", () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); + it("click on is Create new keyspace", () => { + fireEvent.click(screen.getByLabelText("Create new keyspace")); + expect(screen.getByLabelText("Provision keyspace throughput")).toBeDefined(); + }); + it("click on Use existing", () => { + fireEvent.click(screen.getByLabelText("Use existing keyspace")); + }); + + it("Enter Keyspace name ", () => { + fireEvent.change(screen.getByLabelText("Keyspace id"), { target: { value: "unittest1" } }); + expect(screen.getByLabelText("CREATE TABLE unittest1.")).toBeDefined(); + }); +}); diff --git a/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.tsx b/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.tsx new file mode 100644 index 000000000..d7b587d95 --- /dev/null +++ b/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.tsx @@ -0,0 +1,427 @@ +import { Label, Stack, TextField } from "@fluentui/react"; +import React, { FunctionComponent, useEffect, useState } from "react"; +import * as _ from "underscore"; +import * as Constants from "../../../Common/Constants"; +import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils"; +import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip"; +import * as DataModels from "../../../Contracts/DataModels"; +import * as ViewModels from "../../../Contracts/ViewModels"; +import * as AddCollectionUtility from "../../../Shared/AddCollectionUtility"; +import * as SharedConstants from "../../../Shared/Constants"; +import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; +import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; +import { userContext } from "../../../UserContext"; +import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils"; +import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput"; +import Explorer from "../../Explorer"; +import { CassandraAPIDataClient } from "../../Tables/TableDataClient"; +import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; + +export interface CassandraAddCollectionPaneProps { + explorer: Explorer; + closePanel: () => void; + cassandraApiClient: CassandraAPIDataClient; +} + +export const CassandraAddCollectionPane: FunctionComponent = ({ + explorer: container, + closePanel, + cassandraApiClient, +}: CassandraAddCollectionPaneProps) => { + const throughputDefaults = container.collectionCreationDefaults.throughput; + const [createTableQuery, setCreateTableQuery] = useState("CREATE TABLE "); + const [keyspaceId, setKeyspaceId] = useState(""); + const [tableId, setTableId] = useState(""); + const [throughput, setThroughput] = useState( + AddCollectionUtility.getMaxThroughput(container.collectionCreationDefaults, container) + ); + + const [isAutoPilotSelected, setIsAutoPilotSelected] = useState(container.isAutoscaleDefaultEnabled()); + + const [isSharedAutoPilotSelected, setIsSharedAutoPilotSelected] = useState( + container.isAutoscaleDefaultEnabled() + ); + + const [userTableQuery, setUserTableQuery] = useState( + "(userid int, name text, email text, PRIMARY KEY (userid))" + ); + + const [keyspaceHasSharedOffer, setKeyspaceHasSharedOffer] = useState(false); + const [keyspaceIds, setKeyspaceIds] = useState([]); + const [keyspaceThroughput, setKeyspaceThroughput] = useState(throughputDefaults.shared); + const [keyspaceCreateNew, setKeyspaceCreateNew] = useState(true); + const [dedicateTableThroughput, setDedicateTableThroughput] = useState(false); + const [throughputSpendAck, setThroughputSpendAck] = useState(false); + const [sharedThroughputSpendAck, setSharedThroughputSpendAck] = useState(false); + + const { minAutoPilotThroughput: selectedAutoPilotThroughput } = AutoPilotUtils; + const { minAutoPilotThroughput: sharedAutoPilotThroughput } = AutoPilotUtils; + + const _getAutoPilot = (): DataModels.AutoPilotCreationSettings => { + if (keyspaceCreateNew && keyspaceHasSharedOffer && isSharedAutoPilotSelected && sharedAutoPilotThroughput) { + return { + maxThroughput: sharedAutoPilotThroughput * 1, + }; + } + + if (selectedAutoPilotThroughput) { + return { + maxThroughput: selectedAutoPilotThroughput * 1, + }; + } + + return undefined; + }; + + const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier; + + const canConfigureThroughput = !container.isServerlessEnabled(); + + const keyspaceOffers = new Map(); + const [isExecuting, setIsExecuting] = useState(); + const [formErrors, setFormErrors] = useState(""); + + useEffect(() => { + if (keyspaceIds.indexOf(keyspaceId) >= 0) { + setKeyspaceHasSharedOffer(keyspaceOffers.has(keyspaceId)); + } + setCreateTableQuery(`CREATE TABLE ${keyspaceId}.`); + }, [keyspaceId]); + + const addCollectionPaneOpenMessage = { + collection: { + id: tableId, + storage: Constants.BackendDefaults.multiPartitionStorageInGb, + offerThroughput: throughput, + partitionKey: "", + databaseId: keyspaceId, + }, + subscriptionType: userContext.subscriptionType, + subscriptionQuotaId: userContext.quotaId, + defaultsCheck: { + storage: "u", + throughput, + flight: userContext.addCollectionFlight, + }, + dataExplorerArea: Constants.Areas.ContextualPane, + }; + + useEffect(() => { + if (!container.isServerlessEnabled()) { + setIsAutoPilotSelected(container.isAutoscaleDefaultEnabled()); + } + + TelemetryProcessor.trace(Action.CreateCollection, ActionModifiers.Open, addCollectionPaneOpenMessage); + }, []); + + useEffect(() => { + if (container) { + const newKeyspaceIds: ViewModels.Database[] = container.databases(); + const cachedKeyspaceIdsList = _.map(newKeyspaceIds, (keyspace: ViewModels.Database) => { + if (keyspace && keyspace.offer && !!keyspace.offer()) { + keyspaceOffers.set(keyspace.id(), keyspace.offer()); + } + return keyspace.id(); + }); + setKeyspaceIds(cachedKeyspaceIdsList); + } + }, []); + + const _isValid = () => { + const sharedAutoscaleThroughput = sharedAutoPilotThroughput * 1; + if ( + isSharedAutoPilotSelected && + sharedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && + !sharedThroughputSpendAck + ) { + setFormErrors(`Please acknowledge the estimated monthly spend.`); + return false; + } + + const dedicatedAutoscaleThroughput = selectedAutoPilotThroughput * 1; + if ( + isAutoPilotSelected && + dedicatedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && + !throughputSpendAck + ) { + setFormErrors(`Please acknowledge the estimated monthly spend.`); + return false; + } + + if ((keyspaceCreateNew && keyspaceHasSharedOffer && isSharedAutoPilotSelected) || isAutoPilotSelected) { + const autoPilot = _getAutoPilot(); + if ( + !autoPilot || + !autoPilot.maxThroughput || + !AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput) + ) { + setFormErrors( + `Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput` + ); + return false; + } + return true; + } + + if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !throughputSpendAck) { + setFormErrors(`Please acknowledge the estimated daily spend.`); + return false; + } + + if ( + keyspaceHasSharedOffer && + keyspaceCreateNew && + keyspaceThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && + !sharedThroughputSpendAck + ) { + setFormErrors("Please acknowledge the estimated daily spend"); + return false; + } + + return true; + }; + + const onSubmit = async () => { + if (!_isValid()) { + return; + } + setIsExecuting(true); + const autoPilotCommand = `cosmosdb_autoscale_max_throughput`; + + const toCreateKeyspace: boolean = keyspaceCreateNew; + const useAutoPilotForKeyspace: boolean = isSharedAutoPilotSelected && !!sharedAutoPilotThroughput; + const createKeyspaceQueryPrefix = `CREATE KEYSPACE ${keyspaceId.trim()} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 }`; + const createKeyspaceQuery: string = keyspaceHasSharedOffer + ? useAutoPilotForKeyspace + ? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${keyspaceThroughput};` + : `${createKeyspaceQueryPrefix} AND cosmosdb_provisioned_throughput=${keyspaceThroughput};` + : `${createKeyspaceQueryPrefix};`; + let tableQuery: string; + const createTableQueryPrefix = `${createTableQuery}${tableId.trim()} ${userTableQuery}`; + + if (canConfigureThroughput && (dedicateTableThroughput || !keyspaceHasSharedOffer)) { + if (isAutoPilotSelected && selectedAutoPilotThroughput) { + tableQuery = `${createTableQueryPrefix} WITH ${autoPilotCommand}=${throughput};`; + } else { + tableQuery = `${createTableQueryPrefix} WITH cosmosdb_provisioned_throughput=${throughput};`; + } + } else { + tableQuery = `${createTableQueryPrefix};`; + } + + const addCollectionPaneStartMessage = { + ...addCollectionPaneOpenMessage, + collection: { + ...addCollectionPaneOpenMessage.collection, + hasDedicatedThroughput: dedicateTableThroughput, + }, + keyspaceHasSharedOffer, + toCreateKeyspace, + createKeyspaceQuery, + createTableQuery: tableQuery, + }; + + const startKey: number = TelemetryProcessor.traceStart(Action.CreateCollection, addCollectionPaneStartMessage); + try { + if (toCreateKeyspace) { + await cassandraApiClient.createTableAndKeyspace( + userContext?.databaseAccount?.properties?.cassandraEndpoint, + userContext?.databaseAccount?.id, + container, + tableQuery, + createKeyspaceQuery + ); + } else { + await cassandraApiClient.createTableAndKeyspace( + userContext?.databaseAccount?.properties?.cassandraEndpoint, + userContext?.databaseAccount?.id, + container, + tableQuery + ); + } + container.refreshAllDatabases(); + setIsExecuting(false); + closePanel(); + + TelemetryProcessor.traceSuccess(Action.CreateCollection, addCollectionPaneStartMessage, startKey); + } catch (error) { + const errorMessage = getErrorMessage(error); + setFormErrors(errorMessage); + setIsExecuting(false); + const addCollectionPaneFailedMessage = { + ...addCollectionPaneStartMessage, + error: errorMessage, + errorStack: getErrorStack(error), + }; + TelemetryProcessor.traceFailure(Action.CreateCollection, addCollectionPaneFailedMessage, startKey); + } + }; + const handleOnChangeKeyspaceType = (ev: React.FormEvent, mode: string): void => { + setKeyspaceCreateNew(mode === "Create new"); + }; + + const props: RightPaneFormProps = { + expandConsole: () => container.expandConsole(), + formError: formErrors, + isExecuting, + submitButtonText: "Apply", + onSubmit, + }; + return ( + +
+
+

+ +

+ + + handleOnChangeKeyspaceType(e, "Create new")} + /> + Create new + + handleOnChangeKeyspaceType(e, "Use existing")} + /> + Use existing + + + setKeyspaceId(newValue)} + ariaLabel="Keyspace id" + autoFocus + /> + + {keyspaceIds?.map((id: string, index: number) => ( + + ))} + + {canConfigureThroughput && keyspaceCreateNew && ( +
+ setKeyspaceHasSharedOffer(e.target.checked)} + /> + + Provision keyspace throughput + + + Provisioned throughput at the keyspace level will be shared across unlimited number of tables within the + keyspace + +
+ )} + {canConfigureThroughput && keyspaceCreateNew && keyspaceHasSharedOffer && ( +
+ setKeyspaceThroughput(throughput)} + setIsAutoscale={(isAutoscale: boolean) => setIsSharedAutoPilotSelected(isAutoscale)} + onCostAcknowledgeChange={(isAcknowledge: boolean) => { + setSharedThroughputSpendAck(isAcknowledge); + }} + /> +
+ )} +
+
+

+ +

+
+ {createTableQuery} +
+ setTableId(newValue)} + style={{ marginBottom: "5px" }} + /> + setUserTableQuery(newValue)} + /> +
+ + {canConfigureThroughput && keyspaceHasSharedOffer && !keyspaceCreateNew && ( +
+ setDedicateTableThroughput(e.target.checked)} + /> + Provision dedicated throughput for this table + + You can optionally provision dedicated throughput for a table within a keyspace that has throughput + provisioned. This dedicated throughput amount will not be shared with other tables in the keyspace and + does not count towards the throughput you provisioned for the keyspace. This throughput amount will be + billed in addition to the throughput amount you provisioned at the keyspace level. + +
+ )} + {canConfigureThroughput && (!keyspaceHasSharedOffer || dedicateTableThroughput) && ( +
+ setThroughput(throughput)} + setIsAutoscale={(isAutoscale: boolean) => setIsAutoPilotSelected(isAutoscale)} + onCostAcknowledgeChange={(isAcknowledge: boolean) => { + setThroughputSpendAck(isAcknowledge); + }} + /> +
+ )} +
+
+ ); +}; diff --git a/src/Explorer/Panes/CassandraAddCollectionPane/__snapshots__/CassandraAddCollectionPane.test.tsx.snap b/src/Explorer/Panes/CassandraAddCollectionPane/__snapshots__/CassandraAddCollectionPane.test.tsx.snap new file mode 100644 index 000000000..19e1b1e10 --- /dev/null +++ b/src/Explorer/Panes/CassandraAddCollectionPane/__snapshots__/CassandraAddCollectionPane.test.tsx.snap @@ -0,0 +1,164 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CassandraAddCollectionPane Pane should render Default properly 1`] = ` + +
+
+

+ + Keyspace name + + Select an existing keyspace or enter a new keyspace id. + + +

+ + + + Create new + + + + Use existing + + + + +
+ + + Provision keyspace throughput + + + Provisioned throughput at the keyspace level will be shared across unlimited number of tables within the keyspace + +
+
+
+

+ + Enter CQL command to create the table. + + Learn More + + +

+
+ CREATE TABLE +
+ + +
+
+ +
+
+
+`; diff --git a/src/Explorer/Panes/CopyNotebookPane/CopyNotebookPane.tsx b/src/Explorer/Panes/CopyNotebookPane/CopyNotebookPane.tsx index 41b52a189..8026401b6 100644 --- a/src/Explorer/Panes/CopyNotebookPane/CopyNotebookPane.tsx +++ b/src/Explorer/Panes/CopyNotebookPane/CopyNotebookPane.tsx @@ -9,10 +9,7 @@ import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUti import Explorer from "../../Explorer"; import { NotebookContentItem, NotebookContentItemType } from "../../Notebook/NotebookContentItem"; import { ResourceTreeAdapter } from "../../Tree/ResourceTreeAdapter"; -import { - GenericRightPaneComponent, - GenericRightPaneProps, -} from "../GenericRightPaneComponent/GenericRightPaneComponent"; +import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent"; interface Location { @@ -42,7 +39,6 @@ export const CopyNotebookPane: FunctionComponent = ({ }: CopyNotebookPanelProps) => { const [isExecuting, setIsExecuting] = useState(); const [formError, setFormError] = useState(""); - const [formErrorDetail, setFormErrorDetail] = useState(""); const [pinnedRepos, setPinnedRepos] = useState(); const [selectedLocation, setSelectedLocation] = useState(); @@ -92,7 +88,6 @@ export const CopyNotebookPane: FunctionComponent = ({ } catch (error) { const errorMessage = getErrorMessage(error); setFormError(`Failed to copy ${name} to ${destination}`); - setFormErrorDetail(`${errorMessage}`); handleError(errorMessage, "CopyNotebookPaneAdapter/submit", formError); } finally { clearMessage && clearMessage(); @@ -130,14 +125,10 @@ export const CopyNotebookPane: FunctionComponent = ({ setSelectedLocation(option?.data); }; - const genericPaneProps: GenericRightPaneProps = { + const props: RightPaneFormProps = { formError, - formErrorDetail, - id: "copynotebookpane", isExecuting: isExecuting, - title: "Copy notebook", submitButtonText: "OK", - onClose: closePanel, onSubmit: () => submit(), expandConsole: () => container.expandConsole(), }; @@ -149,8 +140,8 @@ export const CopyNotebookPane: FunctionComponent = ({ }; return ( - + - + ); }; diff --git a/src/Explorer/Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane.test.tsx b/src/Explorer/Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane.test.tsx index 619650451..c75f998e3 100644 --- a/src/Explorer/Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane.test.tsx +++ b/src/Explorer/Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane.test.tsx @@ -130,8 +130,8 @@ describe("Delete Collection Confirmation Pane", () => { .hostNodes() .simulate("change", { target: { value: selectedCollectionId } }); - expect(wrapper.exists(".genericPaneSubmitBtn")).toBe(true); - wrapper.find(".genericPaneSubmitBtn").hostNodes().simulate("click"); + expect(wrapper.exists("#sidePanelOkButton")).toBe(true); + wrapper.find("#sidePanelOkButton").hostNodes().simulate("submit"); expect(deleteCollection).toHaveBeenCalledWith(databaseId, selectedCollectionId); wrapper.unmount(); @@ -151,8 +151,8 @@ describe("Delete Collection Confirmation Pane", () => { .hostNodes() .simulate("change", { target: { value: feedbackText } }); - expect(wrapper.exists(".genericPaneSubmitBtn")).toBe(true); - wrapper.find(".genericPaneSubmitBtn").hostNodes().simulate("click"); + expect(wrapper.exists("#sidePanelOkButton")).toBe(true); + wrapper.find("#sidePanelOkButton").hostNodes().simulate("submit"); expect(deleteCollection).toHaveBeenCalledWith(databaseId, selectedCollectionId); const deleteFeedback = new DeleteFeedback( diff --git a/src/Explorer/Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane.tsx b/src/Explorer/Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane.tsx index 1fdc2488b..5fe8c76f5 100644 --- a/src/Explorer/Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane.tsx +++ b/src/Explorer/Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane.tsx @@ -12,10 +12,7 @@ import { userContext } from "../../../UserContext"; import { getCollectionName } from "../../../Utils/APITypeUtils"; import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; import Explorer from "../../Explorer"; -import { - GenericRightPaneComponent, - GenericRightPaneProps, -} from "../GenericRightPaneComponent/GenericRightPaneComponent"; +import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; export interface DeleteCollectionConfirmationPaneProps { explorer: Explorer; closePanel: () => void; @@ -35,7 +32,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent => { + const onSubmit = async (): Promise => { const collection = explorer.findSelectedCollection(); if (!collection || inputCollectionName !== collection.id()) { const errorMessage = "Input " + collectionName + " name does not match the selected " + collectionName; @@ -100,19 +97,15 @@ export const DeleteCollectionConfirmationPane: FunctionComponent explorer.expandConsole(), }; return ( - +
@@ -150,6 +143,6 @@ export const DeleteCollectionConfirmationPane: FunctionComponent
- + ); }; diff --git a/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap b/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap index 2cf3a5962..0157faed6 100644 --- a/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap +++ b/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap @@ -15,70 +15,62 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect } } > - -
-
- Delete container + * - + + Confirm by typing the + container + id + + + - - *": Object { - "left": 0, - "position": "relative", - "top": 0, - }, - }, - "textAlign": "center", - "textDecoration": "none", - "userSelect": "none", - }, - Object { - "backgroundColor": "transparent", - "border": "none", - "color": "#0078d4", - "height": "32px", - "padding": "0 4px", - "width": "32px", - }, - ], - "rootChecked": Object { - "backgroundColor": "#edebe9", - "color": "#005a9e", - }, - "rootCheckedHovered": Object { - "backgroundColor": "#e1dfdd", - "color": "#005a9e", - }, - "rootDisabled": Array [ - Object { - "outline": "transparent", - "position": "relative", - "selectors": Object { - ".ms-Fabric--isFocusVisible &:focus:after": Object { - "border": "1px solid transparent", - "bottom": 2, - "content": "\\"\\"", - "left": 2, - "outline": "1px solid #605e5c", - "position": "absolute", - "right": 2, - "selectors": Object { - "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { - "bottom": -2, - "left": -2, - "outlineColor": "ButtonText", - "right": -2, - "top": -2, - }, - }, - "top": 2, - "zIndex": 1, - }, - "::-moz-focus-inner": Object { - "border": "0", - }, - }, - }, - Object { - "backgroundColor": "#f3f2f1", - "borderColor": "#f3f2f1", - "color": "#a19f9d", - "cursor": "default", - "selectors": Object { - ":focus": Object { - "outline": 0, - }, - ":hover": Object { - "outline": 0, - }, - }, - }, - Object { - "color": "#c8c6c4", - }, - ], - "rootExpanded": Object { - "backgroundColor": "#edebe9", - "color": "#005a9e", - }, - "rootHasMenu": Object { - "width": "auto", - }, - "rootHovered": Object { - "backgroundColor": "#f3f2f1", - "color": "#106ebe", - "selectors": Object { - "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { - "borderColor": "Highlight", - "color": "Highlight", - }, - }, - }, - "rootPressed": Object { - "backgroundColor": "#edebe9", - "color": "#005a9e", - }, - "screenReaderText": Object { - "border": 0, - "height": 1, - "margin": -1, - "overflow": "hidden", - "padding": 0, - "position": "absolute", - "width": 1, - }, - "splitButtonContainer": Array [ - Object { - "outline": "transparent", - "position": "relative", - "selectors": Object { - ".ms-Fabric--isFocusVisible &:focus:after": Object { - "border": "1px solid #ffffff", - "bottom": 3, - "content": "\\"\\"", - "left": 3, - "outline": "1px solid #605e5c", - "position": "absolute", - "right": 3, - "selectors": Object { - "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { - "border": "none", - "bottom": -2, - "left": -2, - "right": -2, - "top": -2, - }, - }, - "top": 3, - "zIndex": 1, - }, - "::-moz-focus-inner": Object { - "border": "0", - }, - }, - }, - Object { - "display": "inline-flex", - "selectors": Object { - ".ms-Button--default": Object { - "borderBottomRightRadius": "0", - "borderRight": "none", - "borderTopRightRadius": "0", - }, - ".ms-Button--primary": Object { - "border": "none", - "borderBottomRightRadius": "0", - "borderTopRightRadius": "0", - "selectors": Object { - "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { - "MsHighContrastAdjust": "none", - "backgroundColor": "Window", - "border": "1px solid WindowText", - "borderRightWidth": "0", - "color": "WindowText", - "forcedColorAdjust": "none", - }, - }, - }, - ".ms-Button--primary + .ms-Button": Object { - "border": "none", - "selectors": Object { - "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { - "border": "1px solid WindowText", - "borderLeftWidth": "0", - }, - }, - }, - }, - }, - ], - "splitButtonContainerChecked": Object { - "selectors": Object { - ".ms-Button--primary": Object { - "selectors": Object { - "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { - "MsHighContrastAdjust": "none", - "backgroundColor": "WindowText", - "color": "Window", - "forcedColorAdjust": "none", - }, - }, - }, - }, - }, - "splitButtonContainerCheckedHovered": Object { - "selectors": Object { - ".ms-Button--primary": Object { - "selectors": Object { - "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { - "MsHighContrastAdjust": "none", - "backgroundColor": "WindowText", - "color": "Window", - "forcedColorAdjust": "none", - }, - }, - }, - }, - }, - "splitButtonContainerDisabled": Object { - "border": "none", - "outline": "none", - "selectors": Object { - "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { - "MsHighContrastAdjust": "none", - "backgroundColor": "Window", - "borderColor": "GrayText", - "color": "GrayText", - "forcedColorAdjust": "none", - }, - }, - }, - "splitButtonContainerFocused": Object { - "outline": "none!important", - }, - "splitButtonContainerHovered": Object { - "selectors": Object { - ".ms-Button--primary": Object { - "selectors": Object { - "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { - "backgroundColor": "Highlight", - "color": "Window", - }, - }, - }, - ".ms-Button.is-disabled": Object { - "color": "#a19f9d", - "selectors": Object { - "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { - "backgroundColor": "Window", - "borderColor": "GrayText", - "color": "GrayText", - }, - }, - }, - }, - }, - "splitButtonDivider": Object { - "bottom": 8, - "position": "absolute", - "right": 31, - "selectors": Object { - "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { - "backgroundColor": "WindowText", - }, - }, - "top": 8, - "width": 1, - }, - "splitButtonDividerDisabled": Object { - "bottom": 8, - "position": "absolute", - "right": 31, - "selectors": Object { - "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { - "backgroundColor": "GrayText", - }, - }, - "top": 8, - "width": 1, - }, - "splitButtonFlexContainer": Object { - "alignItems": "center", - "display": "flex", - "flexWrap": "nowrap", - "height": "100%", - "justifyContent": "center", - }, - "splitButtonMenuButton": Object { - "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { - ".ms-Button-menuIcon": Object { - "color": "WindowText", - }, - }, - "border": "1px solid #8a8886", - "borderBottomRightRadius": "2px", - "borderLeft": "none", - "borderRadius": 0, - "borderTopRightRadius": "2px", - "boxSizing": "border-box", - "cursor": "pointer", - "display": "inline-block", - "height": "auto", - "marginBottom": 0, - "marginLeft": -1, - "marginRight": 0, - "marginTop": 0, - "outline": "transparent", - "padding": 6, - "textAlign": "center", - "textDecoration": "none", - "userSelect": "none", - "verticalAlign": "top", - "width": 32, - }, - "splitButtonMenuButtonDisabled": Object { - "border": "none", - "pointerEvents": "none", - "selectors": Object { - ".ms-Button--primary": Object { - "selectors": Object { - "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { - "backgroundColor": "Window", - "borderColor": "GrayText", - "color": "GrayText", - }, - }, - }, - ".ms-Button-menuIcon": Object { - "selectors": Object { - "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { - "color": "GrayText", - }, - }, - }, - ":hover": Object { - "cursor": "default", - }, - "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { - "backgroundColor": "Window", - "border": "1px solid GrayText", - "color": "GrayText", - }, - }, - }, - "splitButtonMenuFocused": Object { - "outline": "transparent", - "position": "relative", - "selectors": Object { - ".ms-Fabric--isFocusVisible &:focus:after": Object { - "border": "1px solid #ffffff", - "bottom": 3, - "content": "\\"\\"", - "left": 3, - "outline": "1px solid #605e5c", - "position": "absolute", - "right": 3, - "selectors": Object { - "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { - "border": "none", - "bottom": -2, - "left": -2, - "right": -2, - "top": -2, - }, - }, - "top": 3, - "zIndex": 1, - }, - "::-moz-focus-inner": Object { - "border": "0", - }, - }, - }, - "textContainer": Object { - "display": "block", - "flexGrow": 1, - }, - } - } - tabIndex={0} - theme={ - Object { - "disableGlobalClassNames": false, - "effects": Object { - "elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)", - "elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)", - "elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)", - "elevation8": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)", - "roundedCorner2": "2px", - "roundedCorner4": "4px", - "roundedCorner6": "6px", - }, - "fonts": Object { - "large": Object { - "MozOsxFontSmoothing": "grayscale", - "WebkitFontSmoothing": "antialiased", - "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", - "fontSize": "18px", - "fontWeight": 400, - }, - "medium": Object { - "MozOsxFontSmoothing": "grayscale", - "WebkitFontSmoothing": "antialiased", - "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", - "fontSize": "14px", - "fontWeight": 400, - }, - "mediumPlus": Object { - "MozOsxFontSmoothing": "grayscale", - "WebkitFontSmoothing": "antialiased", - "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", - "fontSize": "16px", - "fontWeight": 400, - }, - "mega": Object { - "MozOsxFontSmoothing": "grayscale", - "WebkitFontSmoothing": "antialiased", - "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", - "fontSize": "68px", - "fontWeight": 600, - }, - "small": Object { - "MozOsxFontSmoothing": "grayscale", - "WebkitFontSmoothing": "antialiased", - "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", - "fontSize": "12px", - "fontWeight": 400, - }, - "smallPlus": Object { - "MozOsxFontSmoothing": "grayscale", - "WebkitFontSmoothing": "antialiased", - "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", - "fontSize": "12px", - "fontWeight": 400, - }, - "superLarge": Object { - "MozOsxFontSmoothing": "grayscale", - "WebkitFontSmoothing": "antialiased", - "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", - "fontSize": "42px", - "fontWeight": 600, - }, - "tiny": Object { - "MozOsxFontSmoothing": "grayscale", - "WebkitFontSmoothing": "antialiased", - "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", - "fontSize": "10px", - "fontWeight": 400, - }, - "xLarge": Object { - "MozOsxFontSmoothing": "grayscale", - "WebkitFontSmoothing": "antialiased", - "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", - "fontSize": "20px", - "fontWeight": 600, - }, - "xLargePlus": Object { - "MozOsxFontSmoothing": "grayscale", - "WebkitFontSmoothing": "antialiased", - "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", - "fontSize": "24px", - "fontWeight": 600, - }, - "xSmall": Object { - "MozOsxFontSmoothing": "grayscale", - "WebkitFontSmoothing": "antialiased", - "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", - "fontSize": "10px", - "fontWeight": 400, - }, - "xxLarge": Object { - "MozOsxFontSmoothing": "grayscale", - "WebkitFontSmoothing": "antialiased", - "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", - "fontSize": "28px", - "fontWeight": 600, - }, - "xxLargePlus": Object { - "MozOsxFontSmoothing": "grayscale", - "WebkitFontSmoothing": "antialiased", - "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", - "fontSize": "32px", - "fontWeight": 600, - }, - }, - "isInverted": false, - "palette": Object { - "accent": "#0078d4", - "black": "#000000", - "blackTranslucent40": "rgba(0,0,0,.4)", - "blue": "#0078d4", - "blueDark": "#002050", - "blueLight": "#00bcf2", - "blueMid": "#00188f", - "green": "#107c10", - "greenDark": "#004b1c", - "greenLight": "#bad80a", - "magenta": "#b4009e", - "magentaDark": "#5c005c", - "magentaLight": "#e3008c", - "neutralDark": "#201f1e", - "neutralLight": "#edebe9", - "neutralLighter": "#f3f2f1", - "neutralLighterAlt": "#faf9f8", - "neutralPrimary": "#323130", - "neutralPrimaryAlt": "#3b3a39", - "neutralQuaternary": "#d2d0ce", - "neutralQuaternaryAlt": "#e1dfdd", - "neutralSecondary": "#605e5c", - "neutralSecondaryAlt": "#8a8886", - "neutralTertiary": "#a19f9d", - "neutralTertiaryAlt": "#c8c6c4", - "orange": "#d83b01", - "orangeLight": "#ea4300", - "orangeLighter": "#ff8c00", - "purple": "#5c2d91", - "purpleDark": "#32145a", - "purpleLight": "#b4a0ff", - "red": "#e81123", - "redDark": "#a4262c", - "teal": "#008272", - "tealDark": "#004b50", - "tealLight": "#00b294", - "themeDark": "#005a9e", - "themeDarkAlt": "#106ebe", - "themeDarker": "#004578", - "themeLight": "#c7e0f4", - "themeLighter": "#deecf9", - "themeLighterAlt": "#eff6fc", - "themePrimary": "#0078d4", - "themeSecondary": "#2b88d8", - "themeTertiary": "#71afe5", - "white": "#ffffff", - "whiteTranslucent40": "rgba(255,255,255,.4)", - "yellow": "#ffb900", - "yellowDark": "#d29200", - "yellowLight": "#fff100", - }, - "rtl": undefined, - "semanticColors": Object { - "accentButtonBackground": "#0078d4", - "accentButtonText": "#ffffff", - "actionLink": "#323130", - "actionLinkHovered": "#201f1e", - "blockingBackground": "#FDE7E9", - "blockingIcon": "#FDE7E9", - "bodyBackground": "#ffffff", - "bodyBackgroundChecked": "#edebe9", - "bodyBackgroundHovered": "#f3f2f1", - "bodyDivider": "#edebe9", - "bodyFrameBackground": "#ffffff", - "bodyFrameDivider": "#edebe9", - "bodyStandoutBackground": "#faf9f8", - "bodySubtext": "#605e5c", - "bodyText": "#323130", - "bodyTextChecked": "#000000", - "buttonBackground": "#ffffff", - "buttonBackgroundChecked": "#c8c6c4", - "buttonBackgroundCheckedHovered": "#edebe9", - "buttonBackgroundDisabled": "#f3f2f1", - "buttonBackgroundHovered": "#f3f2f1", - "buttonBackgroundPressed": "#edebe9", - "buttonBorder": "#8a8886", - "buttonBorderDisabled": "#f3f2f1", - "buttonText": "#323130", - "buttonTextChecked": "#201f1e", - "buttonTextCheckedHovered": "#000000", - "buttonTextDisabled": "#a19f9d", - "buttonTextHovered": "#201f1e", - "buttonTextPressed": "#201f1e", - "cardShadow": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)", - "cardShadowHovered": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)", - "cardStandoutBackground": "#ffffff", - "defaultStateBackground": "#faf9f8", - "disabledBackground": "#f3f2f1", - "disabledBodySubtext": "#c8c6c4", - "disabledBodyText": "#a19f9d", - "disabledBorder": "#c8c6c4", - "disabledSubtext": "#d2d0ce", - "disabledText": "#a19f9d", - "errorBackground": "#FDE7E9", - "errorIcon": "#A80000", - "errorText": "#a4262c", - "focusBorder": "#605e5c", - "infoBackground": "#f3f2f1", - "infoIcon": "#605e5c", - "inputBackground": "#ffffff", - "inputBackgroundChecked": "#0078d4", - "inputBackgroundCheckedHovered": "#005a9e", - "inputBorder": "#605e5c", - "inputBorderHovered": "#323130", - "inputFocusBorderAlt": "#0078d4", - "inputForegroundChecked": "#ffffff", - "inputIcon": "#0078d4", - "inputIconDisabled": "#a19f9d", - "inputIconHovered": "#005a9e", - "inputPlaceholderBackgroundChecked": "#deecf9", - "inputPlaceholderText": "#605e5c", - "inputText": "#323130", - "inputTextHovered": "#201f1e", - "link": "#0078d4", - "linkHovered": "#004578", - "listBackground": "#ffffff", - "listHeaderBackgroundHovered": "#f3f2f1", - "listHeaderBackgroundPressed": "#edebe9", - "listItemBackgroundChecked": "#edebe9", - "listItemBackgroundCheckedHovered": "#e1dfdd", - "listItemBackgroundHovered": "#f3f2f1", - "listText": "#323130", - "listTextColor": "#323130", - "menuBackground": "#ffffff", - "menuDivider": "#c8c6c4", - "menuHeader": "#0078d4", - "menuIcon": "#0078d4", - "menuItemBackgroundChecked": "#edebe9", - "menuItemBackgroundHovered": "#f3f2f1", - "menuItemBackgroundPressed": "#edebe9", - "menuItemText": "#323130", - "menuItemTextHovered": "#201f1e", - "messageLink": "#005A9E", - "messageLinkHovered": "#004578", - "messageText": "#323130", - "primaryButtonBackground": "#0078d4", - "primaryButtonBackgroundDisabled": "#f3f2f1", - "primaryButtonBackgroundHovered": "#106ebe", - "primaryButtonBackgroundPressed": "#005a9e", - "primaryButtonBorder": "transparent", - "primaryButtonText": "#ffffff", - "primaryButtonTextDisabled": "#d2d0ce", - "primaryButtonTextHovered": "#ffffff", - "primaryButtonTextPressed": "#ffffff", - "severeWarningBackground": "#FED9CC", - "severeWarningIcon": "#D83B01", - "smallInputBorder": "#605e5c", - "successBackground": "#DFF6DD", - "successIcon": "#107C10", - "successText": "#107C10", - "variantBorder": "#edebe9", - "variantBorderHovered": "#a19f9d", - "warningBackground": "#FFF4CE", - "warningHighlight": "#ffb900", - "warningIcon": "#797775", - "warningText": "#323130", - }, - "spacing": Object { - "l1": "20px", - "l2": "32px", - "m": "16px", - "s1": "8px", - "s2": "4px", - }, - } - } - title="Close pane" - variantClassName="ms-Button--icon" +
- - - - - + +
+
+
+ +
-
-
+ -
- - * - - - - Confirm by typing the - container - id - - - - -
-
-
- -
-
-
-
-
-
-
- - - Help us improve Azure Cosmos DB! - - - - - What is the reason why you are deleting this - container - ? - - - - -
-
-
-