diff --git a/.eslintignore b/.eslintignore index 8c86e0e52..7a5d06bbf 100644 --- a/.eslintignore +++ b/.eslintignore @@ -191,5 +191,4 @@ src/Explorer/Notebook/NotebookRenderer/decorators/kbd-shortcuts/index.tsx src/Explorer/Notebook/temp/inputs/connected-editors/codemirror.tsx src/Explorer/Tree/ResourceTreeAdapter.tsx __mocks__/monaco-editor.ts -src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx src/Explorer/Tree/ResourceTree.tsx \ No newline at end of file diff --git a/less/resourceTree.less b/less/resourceTree.less index cac3f049f..39bced9da 100644 --- a/less/resourceTree.less +++ b/less/resourceTree.less @@ -2,6 +2,7 @@ .dataResourceTree { margin-left: @MediumSpace; + overflow: auto; .databaseHeader { font-size: 14px; diff --git a/less/tree.less b/less/tree.less index e60bcf69c..ed0fbf71f 100644 --- a/less/tree.less +++ b/less/tree.less @@ -1,273 +1,270 @@ @import "./Common/Constants"; - .resourceTree { + height: 100%; + flex: 0 0 auto; + .main { height: 100%; - width: 20%; - flex: 0 0 auto; - .main { - height: 100%; - } + } } .resourceTreeScroll { - height: 100%; - display: flex; - overflow-y: auto; - overflow-x: hidden; - padding-right: 10px; + height: 100%; + display: flex; + overflow-y: auto; + overflow-x: hidden; + padding-right: 10px; } .userSelectNone { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; } .treeHovermargin { - margin-left: 16px; + margin-left: 16px; } .highlight { - padding: @SmallSpace 2px; - outline: 0; + padding: @SmallSpace 2px; + outline: 0; - &:hover { - .hover(); - } + &:hover { + .hover(); + } - &:active { - .active(); - } + &:active { + .active(); + } - &:focus { - .focus(); - } + &:focus { + .focus(); + } } .contextmenushowing { - background-color: #EEE; + background-color: #eee; } .collectionstree { - width: 100%; - margin-top: @DefaultSpace; + width: 100%; + margin-top: @DefaultSpace; + .databaseList { + list-style-type: none; + padding-left: 0px; - .databaseList { - list-style-type: none; - padding-left: 0px; - - .collectionList { - padding-left:(2 * @MediumSpace); - } - - .collectionChildList { - padding-left: @LargeSpace; - } - - .databaseDocuments { - padding-left: (5 * @MediumSpace); - } + .collectionList { + padding-left: (2 * @MediumSpace); } + + .collectionChildList { + padding-left: @LargeSpace; + } + + .databaseDocuments { + padding-left: (5 * @MediumSpace); + } + } } .pointerCursor { - cursor: pointer; + cursor: pointer; } .menuEllipsis { - padding-right: 6px; - font-weight: bold; - font-size: 18px; - position: relative; - top: -5px; - left: 0px; - float: right; - display: none; - padding-left: 6px!important; - line-height: @TreeLineHeight; + padding-right: 6px; + font-weight: bold; + font-size: 18px; + position: relative; + top: -5px; + left: 0px; + float: right; + display: none; + padding-left: 6px !important; + line-height: @TreeLineHeight; } .databaseMenu { - .flex-display(); + .flex-display(); } .databaseMenu:hover .menuEllipsis, .databaseMenu:focus .menuEllipsis { - display: block; + display: block; } .databaseCollChildTextOverflow { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - flex: 1; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + flex: 1; } .collectionMenu { - .flex-display(); + .flex-display(); } .collectionMenu:hover .menuEllipsis, .collectionMenu:focus .menuEllipsis { - display: block; + display: block; } .documentsMenu:hover .menuEllipsis, .documentsMenu:focus .menuEllipsis { - display: block; + display: block; } .treeChildMenu { - display: flex; + display: flex; } .storedProcedureMenu:hover .menuEllipsis, .storedProcedureMenu:focus .menuEllipsis { - display: block; + display: block; } .childMenu { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - padding-left: (6 * @MediumSpace); - width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + padding-left: (6 * @MediumSpace); + width: 100%; } .storedChildMenu:hover .menuEllipsis, .storedChildMenu:focus .menuEllipsis { - display: block; + display: block; } .contextmenu6 { - top: -29px; + top: -29px; } .userDefinedMenu:hover .contextmenu6 { - display: block; + display: block; } .userDefinedchildMenu:hover .menuEllipsis, .userDefinedchildMenu:focus .menuEllipsis { - display: block; + display: block; } .triggersMenu:hover .menuEllipsis, .triggersMenu:focus .menuEllipsis { - display: block; + display: block; } .triggersChildMenu:hover .menuEllipsis, .triggersChildMenu:focus .menuEllipsis { - display: block; + display: block; } .databaseId { - font-size: 14px; + font-size: 14px; } .storedUdfTriggerMenu { - padding-left: 0px; + padding-left: 0px; } .collectionstree img { - width: 16px; - height: 16px; - vertical-align: text-top; + width: 16px; + height: 16px; + vertical-align: text-top; } img.collectionsTreeCollapseExpand { - width: 10px; - height: 10px; - vertical-align: middle; - margin-bottom: 5px; + width: 10px; + height: 10px; + vertical-align: middle; + margin-bottom: 5px; } .collapsed::before { - content: "\23F5"; - margin-left: 0px; - font-size: 15px; + content: "\23F5"; + margin-left: 0px; + font-size: 15px; } .expanded::before { - content: '\23F7'; - margin-left: 0px; - font-size: 15px; + content: "\23F7"; + margin-left: 0px; + font-size: 15px; } .collectionMenuChildren { - padding-left: 42px; + padding-left: 42px; } .main-nav { - width: 100vh; - height: 40px; - background: white; - transform-origin: left top; - -webkit-transform-origin: left top; - -ms-transform-origin: left top; - transform: rotate(-90deg) translateX(-100%); - -webkit-transform: rotate(-90deg) translateX(-100%); - -ms-transform: rotate(-90deg) translateX(-100%); - border-bottom: 1px solid #CCC; + width: 100vh; + height: 40px; + background: white; + transform-origin: left top; + -webkit-transform-origin: left top; + -ms-transform-origin: left top; + transform: rotate(-90deg) translateX(-100%); + -webkit-transform: rotate(-90deg) translateX(-100%); + -ms-transform: rotate(-90deg) translateX(-100%); + border-bottom: 1px solid #ccc; } .main-nav-img { - width: 16px; - height: 16px; - margin: -32px 0 0 0; - transform: rotate(-90deg) translateX(-100%); - -webkit-transform: rotate(-90deg) translateX(-100%); - -ms-transform: rotate(-90deg) translateX(-100%); + width: 16px; + height: 16px; + margin: -32px 0 0 0; + transform: rotate(-90deg) translateX(-100%); + -webkit-transform: rotate(-90deg) translateX(-100%); + -ms-transform: rotate(-90deg) translateX(-100%); } .main-nav-img.main-nav-sub-img { - width: 16px; - height: 16px; - margin: 0px 0px 0 0; - transform: rotate(180deg) translateX(0%); - -webkit-transform: rotate(180deg) translateX(0%); - -ms-transform: rotate(180deg) translateX(0%); - position: absolute; - right: -8px; - top: 16px; + width: 16px; + height: 16px; + margin: 0px 0px 0 0; + transform: rotate(180deg) translateX(0%); + -webkit-transform: rotate(180deg) translateX(0%); + -ms-transform: rotate(180deg) translateX(0%); + position: absolute; + right: -8px; + top: 16px; } ul.nav { - margin: 0 auto; - margin-top: 0px; - margin-left: 0px; + margin: 0 auto; + margin-top: 0px; + margin-left: 0px; } .mini ul.nav li { - float: right; - line-height: 25px; - height: auto; - margin-top: 3px; + float: right; + line-height: 25px; + height: auto; + margin-top: 3px; } .spancolchildstyle { - padding: 4px; + padding: 4px; } .contextmenubutton { - float: right; - display: none; + float: right; + display: none; } -.highlight:hover>.contextmenubutton { - display: unset; +.highlight:hover > .contextmenubutton { + display: unset; } -.highlight:hover>.contextmenubutton::after { - content: "\2026"; - font-size: 12px; +.highlight:hover > .contextmenubutton::after { + content: "\2026"; + font-size: 12px; } .showEllipsis { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} \ No newline at end of file + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} diff --git a/src/Common/ResourceTreeContainer.tsx b/src/Common/ResourceTreeContainer.tsx index 129a38115..fe04f9e04 100644 --- a/src/Common/ResourceTreeContainer.tsx +++ b/src/Common/ResourceTreeContainer.tsx @@ -3,6 +3,7 @@ import arrowLeftImg from "../../images/imgarrowlefticon.svg"; import refreshImg from "../../images/refresh-cosmos.svg"; import { AuthType } from "../AuthType"; import Explorer from "../Explorer/Explorer"; +import { ResourceTokenTree } from "../Explorer/Tree/ResourceTokenTree"; import { ResourceTree } from "../Explorer/Tree/ResourceTree"; import { userContext } from "../UserContext"; @@ -52,7 +53,7 @@ export const ResourceTreeContainer: FunctionComponent {userContext.authType === AuthType.ResourceToken ? ( -
+ ) : userContext.features.enableKOResourceTree ? (
) : ( diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap index cb8ecbfaf..b1164fe05 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap @@ -44,10 +44,6 @@ exports[`SettingsComponent renders 1`] = ` "copyNotebook": [Function], "parameters": [Function], }, - "resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken { - "container": [Circular], - "parameters": [Function], - }, }, "databaseId": "test", "defaultTtl": [Function], @@ -115,10 +111,6 @@ exports[`SettingsComponent renders 1`] = ` "copyNotebook": [Function], "parameters": [Function], }, - "resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken { - "container": [Circular], - "parameters": [Function], - }, }, "databaseId": "test", "defaultTtl": [Function], diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index ac1e077d6..79f6fec11 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -59,7 +59,6 @@ import TerminalTab from "./Tabs/TerminalTab"; import Database from "./Tree/Database"; import ResourceTokenCollection from "./Tree/ResourceTokenCollection"; import { ResourceTreeAdapter } from "./Tree/ResourceTreeAdapter"; -import { ResourceTreeAdapterForResourceToken } from "./Tree/ResourceTreeAdapterForResourceToken"; import StoredProcedure from "./Tree/StoredProcedure"; import { useDatabases } from "./useDatabases"; import { useSelectedNode } from "./useSelectedNode"; @@ -74,9 +73,6 @@ export default class Explorer { // Resource Tree private resourceTree: ResourceTreeAdapter; - // Resource Token - public resourceTreeForResourceToken: ResourceTreeAdapterForResourceToken; - // Tabs public isTabsContentExpanded: ko.Observable; @@ -186,7 +182,6 @@ export default class Explorer { ); this.resourceTree = new ResourceTreeAdapter(this); - this.resourceTreeForResourceToken = new ResourceTreeAdapterForResourceToken(this); // Override notebook server parameters from URL parameters if (userContext.features.notebookServerUrl && userContext.features.notebookServerToken) { diff --git a/src/Explorer/Notebook/NotebookManager.tsx b/src/Explorer/Notebook/NotebookManager.tsx index d6d13b6a1..4d14abf6a 100644 --- a/src/Explorer/Notebook/NotebookManager.tsx +++ b/src/Explorer/Notebook/NotebookManager.tsx @@ -29,6 +29,7 @@ import { SnapshotRequest } from "./NotebookComponent/types"; import { NotebookContainerClient } from "./NotebookContainerClient"; import { NotebookContentClient } from "./NotebookContentClient"; import { SchemaAnalyzerNotebook } from "./SchemaAnalyzer/SchemaAnalyzerUtils"; +import { useNotebook } from "./useNotebook"; type NotebookPaneContent = string | ImmutableNotebook; @@ -110,6 +111,7 @@ export default class NotebookManager { this.junoClient.subscribeToPinnedRepos((pinnedRepos) => { this.params.resourceTree.initializeGitHubRepos(pinnedRepos); this.params.resourceTree.triggerRender(); + useNotebook.getState().initializeGitHubRepos(pinnedRepos); }); this.refreshPinnedRepos(); } diff --git a/src/Explorer/Notebook/useNotebook.ts b/src/Explorer/Notebook/useNotebook.ts index c4967a228..af1b47477 100644 --- a/src/Explorer/Notebook/useNotebook.ts +++ b/src/Explorer/Notebook/useNotebook.ts @@ -6,10 +6,12 @@ import { getErrorMessage } from "../../Common/ErrorHandlingUtils"; import * as Logger from "../../Common/Logger"; import { configContext } from "../../ConfigContext"; import * as DataModels from "../../Contracts/DataModels"; +import { IPinnedRepo } from "../../Juno/JunoClient"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import { userContext } from "../../UserContext"; import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils"; +import * as GitHubUtils from "../../Utils/GitHubUtils"; import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem"; import NotebookManager from "./NotebookManager"; @@ -39,6 +41,7 @@ interface NotebookState { updateNotebookItem: (item: NotebookContentItem) => void; deleteNotebookItem: (item: NotebookContentItem) => void; initializeNotebooksTree: (notebookManager: NotebookManager) => Promise; + initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void; } export const useNotebook: UseStore = create((set, get) => ({ @@ -202,4 +205,31 @@ export const useNotebook: UseStore = create((set, get) => ({ } } }, + initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]): void => { + const gitHubNotebooksContentRoot = cloneDeep(get().gitHubNotebooksContentRoot); + if (gitHubNotebooksContentRoot) { + gitHubNotebooksContentRoot.children = []; + pinnedRepos?.forEach((pinnedRepo) => { + const repoFullName = GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name); + const repoTreeItem: NotebookContentItem = { + name: repoFullName, + path: "PsuedoDir", + type: NotebookContentItemType.Directory, + children: [], + }; + + pinnedRepo.branches.forEach((branch) => { + repoTreeItem.children.push({ + name: branch.name, + path: GitHubUtils.toContentUri(pinnedRepo.owner, pinnedRepo.name, branch.name, ""), + type: NotebookContentItemType.Directory, + }); + }); + + gitHubNotebooksContentRoot.children.push(repoTreeItem); + }); + + set({ gitHubNotebooksContentRoot }); + } + }, })); diff --git a/src/Explorer/Panes/GitHubReposPanel/__snapshots__/GitHubReposPanel.test.tsx.snap b/src/Explorer/Panes/GitHubReposPanel/__snapshots__/GitHubReposPanel.test.tsx.snap index 9d1b8cc0c..7406a14ee 100644 --- a/src/Explorer/Panes/GitHubReposPanel/__snapshots__/GitHubReposPanel.test.tsx.snap +++ b/src/Explorer/Panes/GitHubReposPanel/__snapshots__/GitHubReposPanel.test.tsx.snap @@ -33,10 +33,6 @@ exports[`GitHub Repos Panel should render Default properly 1`] = ` "copyNotebook": [Function], "parameters": [Function], }, - "resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken { - "container": [Circular], - "parameters": [Function], - }, }, "getRepo": [Function], "pinRepo": [Function], diff --git a/src/Explorer/Panes/PanelComponent.less b/src/Explorer/Panes/PanelComponent.less index 2421fe68b..acf5d3d2c 100644 --- a/src/Explorer/Panes/PanelComponent.less +++ b/src/Explorer/Panes/PanelComponent.less @@ -150,9 +150,6 @@ .backImageIcon { margin-top: 8px; } -.entityValueTextField { - margin: 24px; -} .addEntityDatePicker { max-width: 145px; } diff --git a/src/Explorer/Panes/StringInputPane/__snapshots__/StringInputPane.test.tsx.snap b/src/Explorer/Panes/StringInputPane/__snapshots__/StringInputPane.test.tsx.snap index 27f1f941d..80bb050de 100644 --- a/src/Explorer/Panes/StringInputPane/__snapshots__/StringInputPane.test.tsx.snap +++ b/src/Explorer/Panes/StringInputPane/__snapshots__/StringInputPane.test.tsx.snap @@ -23,10 +23,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = ` "copyNotebook": [Function], "parameters": [Function], }, - "resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken { - "container": [Circular], - "parameters": [Function], - }, } } inProgressMessage="Creating directory " diff --git a/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx b/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx index 402e87da6..eafa4ae1b 100644 --- a/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx +++ b/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx @@ -218,7 +218,7 @@ export const AddTableEntityPanel: FunctionComponent = if (isEntityValuePanelOpen) { return ( - + back setIsEntityValuePanelFalse()} /> diff --git a/src/Explorer/Panes/Tables/EditTableEntityPanel.tsx b/src/Explorer/Panes/Tables/EditTableEntityPanel.tsx index f71a1a829..914aaf724 100644 --- a/src/Explorer/Panes/Tables/EditTableEntityPanel.tsx +++ b/src/Explorer/Panes/Tables/EditTableEntityPanel.tsx @@ -1,11 +1,11 @@ -import { IDropdownOption, Image, IPanelProps, IRenderFunction, Label, Stack, Text, TextField } from "@fluentui/react"; +import { IDropdownOption, Image, Label, Stack, Text, TextField } from "@fluentui/react"; import { useBoolean } from "@fluentui/react-hooks"; import React, { FunctionComponent, useEffect, useState } from "react"; import * as _ from "underscore"; import AddPropertyIcon from "../../../../images/Add-property.svg"; import RevertBackIcon from "../../../../images/RevertBack.svg"; +import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils"; import { TableEntity } from "../../../Common/TableEntity"; -import { useSidePanel } from "../../../hooks/useSidePanel"; import { userContext } from "../../../UserContext"; import * as TableConstants from "../../Tables/Constants"; import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities"; @@ -14,7 +14,7 @@ import * as Entities from "../../Tables/Entities"; import { CassandraAPIDataClient, TableDataClient } from "../../Tables/TableDataClient"; import * as TableEntityProcessor from "../../Tables/TableEntityProcessor"; import QueryTablesTab from "../../Tabs/QueryTablesTab"; -import { PanelContainerComponent } from "../PanelContainerComponent"; +import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; import { attributeNameLabel, attributeValueLabel, @@ -29,7 +29,6 @@ import { getEntityValuePlaceholder, getFormattedTime, imageProps, - isValidEntities, options, } from "./Validators/EntityTableHelper"; @@ -59,12 +58,13 @@ export const EditTableEntityPanel: FunctionComponent tableEntityListViewModel, cassandraApiClient, }: EditTableEntityPanelProps): JSX.Element => { - const closeSidePanel = useSidePanel((state) => state.closeSidePanel); const [entities, setEntities] = useState([]); const [selectedRow, setSelectedRow] = useState(0); const [entityAttributeValue, setEntityAttributeValue] = useState(""); const [originalDocument, setOriginalDocument] = useState({}); const [entityAttributeProperty, setEntityAttributeProperty] = useState(""); + const [formError, setFormError] = useState(""); + const [isExecuting, setIsExecuting] = useState(false); const [ isEntityValuePanelOpen, @@ -190,26 +190,44 @@ export const EditTableEntityPanel: FunctionComponent return displayValue; }; - const submit = async (event: React.FormEvent): Promise => { - if (!isValidEntities(entities)) { - return undefined; + const onSubmit = async (): Promise => { + for (let i = 0; i < entities.length; i++) { + const { property, type } = entities[i]; + if (property === "" || property === undefined) { + setFormError(`Property name cannot be empty. Please enter a property name`); + return; + } + + if (!type) { + setFormError(`Property type cannot be empty. Please select a type from the dropdown for property ${property}`); + return; + } } - event.preventDefault(); + + setIsExecuting(true); const entity: Entities.ITableEntity = entityFromAttributes(entities); const newTableDataClient = userContext.apiType === "Cassandra" ? cassandraApiClient : tableDataClient; const originalDocumentData = userContext.apiType === "Cassandra" ? originalDocument[0] : originalDocument; - const newEntity: Entities.ITableEntity = await newTableDataClient.updateDocument( - queryTablesTab.collection, - originalDocumentData, - entity - ); - await tableEntityListViewModel.updateCachedEntity(newEntity); - if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) { - tableEntityListViewModel.redrawTableThrottled(); + + try { + const newEntity: Entities.ITableEntity = await newTableDataClient.updateDocument( + queryTablesTab.collection, + originalDocumentData, + entity + ); + await tableEntityListViewModel.updateCachedEntity(newEntity); + if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) { + tableEntityListViewModel.redrawTableThrottled(); + } + tableEntityListViewModel.selected.removeAll(); + tableEntityListViewModel.selected.push(newEntity); + } catch (error) { + const errorMessage = getErrorMessage(error); + handleError(errorMessage, "EditTableRow"); + throw error; + } finally { + setIsExecuting(false); } - tableEntityListViewModel.selected.removeAll(); - tableEntityListViewModel.selected.push(newEntity); - closeSidePanel(); }; const tryInsertNewHeaders = (viewModel: TableEntityListViewModel, newEntity: Entities.ITableEntity): boolean => { @@ -299,109 +317,81 @@ export const EditTableEntityPanel: FunctionComponent setIsEntityValuePanelTrue(); }; - const renderPanelContent = (): JSX.Element => { - return ( -
-
-
- {entities.map((entity, index) => { - return ( - editEntity(index)} - onSelectDate={(date: Date) => { - entityChange(date, index, "value"); - }} - onDeleteEntity={() => deleteEntityAtIndex(index)} - onEntityPropertyChange={(event, newInput?: string) => { - entityChange(newInput, index, "property"); - }} - onEntityTypeChange={(event: React.FormEvent, selectedParam: IDropdownOption) => { - entityTypeChange(event, selectedParam, index); - }} - onEntityValueChange={(event, newInput?: string) => { - entityChange(newInput, index, "value"); - }} - onEntityTimeValueChange={(event, newInput?: string) => { - entityChange(newInput, index, "time"); - }} - /> - ); - })} - {userContext.apiType !== "Cassandra" && ( - - Add Entity - {getAddButtonLabel(userContext.apiType)} - - )} -
- {renderPanelFooter()} -
-
- ); - }; - - const renderPanelFooter = (): JSX.Element => { - return ( -
-
- -
-
- ); - }; - - const onRenderNavigationContent: IRenderFunction = () => ( - - back setIsEntityValuePanelFalse()} /> - - - ); if (isEntityValuePanelOpen) { return ( - { - setEntityAttributeValue(newInput); - entityChange(newInput, selectedRow, "value"); - }} - /> - } - isConsoleExpanded={false} - /> + + + back setIsEntityValuePanelFalse()} /> + + + { + setEntityAttributeValue(newInput); + entityChange(newInput, selectedRow, "value"); + }} + /> + ); } + const props: RightPaneFormProps = { + formError, + isExecuting, + submitButtonText: "Update", + onSubmit, + }; + return ( - + +
+ {entities.map((entity, index) => { + return ( + editEntity(index)} + onSelectDate={(date: Date) => { + entityChange(date, index, "value"); + }} + onDeleteEntity={() => deleteEntityAtIndex(index)} + onEntityPropertyChange={(event, newInput?: string) => { + entityChange(newInput, index, "property"); + }} + onEntityTypeChange={(event: React.FormEvent, selectedParam: IDropdownOption) => { + entityTypeChange(event, selectedParam, index); + }} + onEntityValueChange={(event, newInput?: string) => { + entityChange(newInput, index, "value"); + }} + onEntityTimeValueChange={(event, newInput?: string) => { + entityChange(newInput, index, "time"); + }} + /> + ); + })} + {userContext.apiType !== "Cassandra" && ( + + Add Entity + {getAddButtonLabel(userContext.apiType)} + + )} +
+
); }; diff --git a/src/Explorer/Panes/Tables/Validators/EntityTableHelper.tsx b/src/Explorer/Panes/Tables/Validators/EntityTableHelper.tsx index b5542e86e..b38f569e0 100644 --- a/src/Explorer/Panes/Tables/Validators/EntityTableHelper.tsx +++ b/src/Explorer/Panes/Tables/Validators/EntityTableHelper.tsx @@ -80,7 +80,7 @@ export const int64Placeholder = "Enter a signed 64-bit integer, in the range (-2 export const columnProps: Partial = { tokens: { childrenGap: 10 }, - styles: { root: { width: 680, marginBottom: 8 } }, + styles: { root: { marginBottom: 8 } }, }; // helper functions diff --git a/src/Explorer/Panes/Tables/__snapshots__/EditTableEntityPanel.test.tsx.snap b/src/Explorer/Panes/Tables/__snapshots__/EditTableEntityPanel.test.tsx.snap index 89e86e6f0..018fe7118 100644 --- a/src/Explorer/Panes/Tables/__snapshots__/EditTableEntityPanel.test.tsx.snap +++ b/src/Explorer/Panes/Tables/__snapshots__/EditTableEntityPanel.test.tsx.snap @@ -13,879 +13,1780 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = ` } } > - +
+
-
- - - +
+ Add Entity +
+ + + + Add Property - -
+ +
-
-
- -
-
-
- - } - panelWidth="700px" - > - - +
+ - - + - - -
-
-
-
-
- -
-
-
+ + - - *": Object { + "left": 0, + "position": "relative", + "top": 0, + }, + }, + "textAlign": "center", + "textDecoration": "none", + "userSelect": "none", + }, + Object { + "height": "32px", + "minWidth": "80px", + }, + Object { + "backgroundColor": "#0078d4", + "border": "1px solid #0078d4", + "color": "#ffffff", + "selectors": Object { + ".ms-Fabric--isFocusVisible &:focus": Object { + "selectors": Object { + ":after": Object { + "border": "none", + "outlineColor": "#ffffff", + }, + }, + }, + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "MsHighContrastAdjust": "none", + "backgroundColor": "WindowText", + "borderColor": "WindowText", + "color": "Window", + "forcedColorAdjust": "none", + }, + }, + }, + ], + "rootChecked": Object { + "backgroundColor": "#005a9e", + "color": "#ffffff", + }, + "rootCheckedHovered": Object { + "backgroundColor": "#005a9e", + "color": "#ffffff", + }, + "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 { + "backgroundColor": "#f3f2f1", + "color": "#d2d0ce", + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "backgroundColor": "Window", + "borderColor": "GrayText", + "color": "GrayText", + }, + }, + }, + ], + "rootExpanded": Object { + "backgroundColor": "#005a9e", + "color": "#ffffff", + }, + "rootHovered": Object { + "backgroundColor": "#106ebe", + "border": "1px solid #106ebe", + "color": "#ffffff", + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "backgroundColor": "Highlight", + "borderColor": "Highlight", + "color": "Window", + }, + }, + }, + "rootPressed": Object { + "backgroundColor": "#005a9e", + "border": "1px solid #005a9e", + "color": "#ffffff", + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "MsHighContrastAdjust": "none", + "backgroundColor": "WindowText", + "borderColor": "WindowText", + "color": "Window", + "forcedColorAdjust": "none", + }, + }, + }, + "screenReaderText": Object { + "border": 0, + "height": 1, + "margin": -1, + "overflow": "hidden", + "padding": 0, + "position": "absolute", + "width": 1, + }, + "splitButtonContainer": Array [ + Object { + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "border": "none", + }, + }, + }, + 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": Array [ + Object { + "backgroundColor": "#ffffff", + "bottom": 8, + "position": "absolute", + "right": 31, + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "backgroundColor": "Window", + }, + }, + "top": 8, + "width": 1, + }, + 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": Array [ + Object { + "backgroundColor": "#0078d4", + "color": "#ffffff", + "selectors": Object { + ":hover": Object { + "backgroundColor": "#106ebe", + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "color": "Highlight", + }, + }, + }, + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "backgroundColor": "WindowText", + }, + }, + }, + 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, + }, + ], + "splitButtonMenuButtonChecked": Object { + "backgroundColor": "#005a9e", + "selectors": Object { + ":hover": Object { + "backgroundColor": "#005a9e", + }, + }, + }, + "splitButtonMenuButtonDisabled": Array [ + Object { + "backgroundColor": "#f3f2f1", + "selectors": Object { + ":hover": Object { + "backgroundColor": "#f3f2f1", + }, + }, + }, + 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", + }, + }, + }, + ], + "splitButtonMenuButtonExpanded": Object { + "backgroundColor": "#005a9e", + "selectors": Object { + ":hover": Object { + "backgroundColor": "#005a9e", + }, + }, + }, + "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", + }, + }, + }, + "splitButtonMenuIcon": Object { + "color": "#ffffff", + }, + "splitButtonMenuIconDisabled": Object { + "color": "#a19f9d", + "selectors": Object { + "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { + "color": "GrayText", + }, + }, + }, + "textContainer": Object { + "display": "block", + "flexGrow": 1, + }, + } + } + text="Update" theme={ Object { "disableGlobalClassNames": false, @@ -1159,1926 +2060,48 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = ` }, } } + type="submit" + variantClassName="ms-Button--primary" > -
- -
-
- - -
- - - -
-
-
-
-
-
- Edit Table Entity -
-
- - - *": 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", - }, - "ms-Panel-closeButton ms-PanelAction-close", - Object { - "color": "#605e5c", - "fontSize": "20px", - "marginRight": 14, - }, - false, - ], - "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": Array [ - Object { - "backgroundColor": "#f3f2f1", - "color": "#106ebe", - "selectors": Object { - "@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object { - "borderColor": "Highlight", - "color": "Highlight", - }, - }, - }, - Object { - "color": "#323130", - }, - ], - "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, - }, - } - } - 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" - variantClassName="ms-Button--icon" - > - - - - - -
-
-
-
-
-
-
-
- -
- - -
- Add Entity -
-
-
- - - Add Property - - -
-
-
-
-
- -
-
-
-
-
-
-
-
-
- -
-
- -
- - - - - - - - - + Update + + + + + + + + + + +
+ + + `; diff --git a/src/Explorer/Tabs/QueryTablesTab.tsx b/src/Explorer/Tabs/QueryTablesTab.tsx index aa208fcfd..fb096e1a9 100644 --- a/src/Explorer/Tabs/QueryTablesTab.tsx +++ b/src/Explorer/Tabs/QueryTablesTab.tsx @@ -158,7 +158,8 @@ export default class QueryTablesTab extends TabsBase { queryTablesTab={this} tableEntityListViewModel={this.tableEntityListViewModel()} cassandraApiClient={new CassandraAPIDataClient()} - /> + />, + "700px" ); }; diff --git a/src/Explorer/Tree/ResourceTreeAdapterForResourceToken.tsx b/src/Explorer/Tree/ResourceTokenTree.tsx similarity index 61% rename from src/Explorer/Tree/ResourceTreeAdapterForResourceToken.tsx rename to src/Explorer/Tree/ResourceTokenTree.tsx index 49529f0d7..b405e2559 100644 --- a/src/Explorer/Tree/ResourceTreeAdapterForResourceToken.tsx +++ b/src/Explorer/Tree/ResourceTokenTree.tsx @@ -1,45 +1,18 @@ -import * as ko from "knockout"; -import * as React from "react"; +import React from "react"; import CollectionIcon from "../../../images/tree-collection.svg"; -import { ReactAdapter } from "../../Bindings/ReactBindingHandler"; import * as ViewModels from "../../Contracts/ViewModels"; import { useTabs } from "../../hooks/useTabs"; import { userContext } from "../../UserContext"; import { TreeComponent, TreeNode } from "../Controls/TreeComponent/TreeComponent"; -import Explorer from "../Explorer"; import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter"; import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity"; -import { NotebookContentItem } from "../Notebook/NotebookContentItem"; import { useDatabases } from "../useDatabases"; import { useSelectedNode } from "../useSelectedNode"; -export class ResourceTreeAdapterForResourceToken implements ReactAdapter { - public parameters: ko.Observable; - public myNotebooksContentRoot: NotebookContentItem; +export const ResourceTokenTree: React.FC = (): JSX.Element => { + const collection = useDatabases((state) => state.resourceTokenCollection); - public constructor(private container: Explorer) { - this.parameters = ko.observable(Date.now()); - - useDatabases.subscribe( - () => this.triggerRender(), - (state) => state.resourceTokenCollection - ); - useSelectedNode.subscribe(() => this.triggerRender()); - useTabs.subscribe( - () => this.triggerRender(), - (state) => state.activeTab - ); - - this.triggerRender(); - } - - public renderComponent(): JSX.Element { - const dataRootNode = this.buildCollectionNode(); - return ; - } - - public buildCollectionNode(): TreeNode { - const collection: ViewModels.CollectionBase = useDatabases.getState().resourceTokenCollection; + const buildCollectionNode = (): TreeNode => { if (!collection) { return { label: undefined, @@ -86,9 +59,7 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter { isExpanded: true, children: [collectionNode], }; - } + }; - public triggerRender() { - window.requestAnimationFrame(() => this.parameters(Date.now())); - } -} + return ; +}; diff --git a/src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx b/src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx deleted file mode 100644 index 47133e42a..000000000 --- a/src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { shallow } from "enzyme"; -import React from "react"; -import * as DataModels from "../../Contracts/DataModels"; -import * as ViewModels from "../../Contracts/ViewModels"; -import { TreeComponent, TreeComponentProps, TreeNode } from "../Controls/TreeComponent/TreeComponent"; -import Explorer from "../Explorer"; -import { useDatabases } from "../useDatabases"; -import ResourceTokenCollection from "./ResourceTokenCollection"; -import { ResourceTreeAdapterForResourceToken } from "./ResourceTreeAdapterForResourceToken"; - -describe("Resource tree for resource token", () => { - const mockContainer = {} as Explorer; - const resourceTree = new ResourceTreeAdapterForResourceToken(mockContainer); - const mockCollection = { - _rid: "fakeRid", - _self: "fakeSelf", - id: "fakeId", - } as DataModels.Collection; - const mockResourceTokenCollection: ViewModels.CollectionBase = new ResourceTokenCollection( - mockContainer, - "fakeDatabaseId", - mockCollection - ); - useDatabases.setState({ resourceTokenCollection: mockResourceTokenCollection }); - - it("should render", () => { - const rootNode: TreeNode = resourceTree.buildCollectionNode(); - const props: TreeComponentProps = { - rootNode, - className: "dataResourceTree", - }; - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/src/Explorer/Tree/__snapshots__/ResourceTreeAdapterForResourceToken.test.tsx.snap b/src/Explorer/Tree/__snapshots__/ResourceTreeAdapterForResourceToken.test.tsx.snap deleted file mode 100644 index cb88eaa00..000000000 --- a/src/Explorer/Tree/__snapshots__/ResourceTreeAdapterForResourceToken.test.tsx.snap +++ /dev/null @@ -1,36 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Resource tree for resource token should render 1`] = ` -
- -
-`; diff --git a/src/Explorer/useDatabases.ts b/src/Explorer/useDatabases.ts index 3808f42c5..1f9886a7d 100644 --- a/src/Explorer/useDatabases.ts +++ b/src/Explorer/useDatabases.ts @@ -2,6 +2,7 @@ import _ from "underscore"; import create, { UseStore } from "zustand"; import * as Constants from "../Common/Constants"; import * as ViewModels from "../Contracts/ViewModels"; +import { userContext } from "../UserContext"; import { useSelectedNode } from "./useSelectedNode"; interface DatabasesState { @@ -136,6 +137,11 @@ export const useDatabases: UseStore = create((set, get) => ({ }, validateCollectionId: async (databaseId: string, collectionId: string): Promise => { const database = get().databases.find((db) => db.id() === databaseId); + // For a new tables account, database is undefined when creating the first table + if (!database && userContext.apiType === "Tables") { + return true; + } + await database.loadCollections(); return !database.collections().some((collection) => collection.id() === collectionId); }, diff --git a/src/Index.ts b/src/Index.ts deleted file mode 100644 index 9eb33943c..000000000 --- a/src/Index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import "../less/index.less"; -import "./Libs/jquery"; - -import * as ko from "knockout"; - -class Index { - public navigationSelection: ko.Observable; - - constructor() { - this.navigationSelection = ko.observable("quickstart"); - } - - public quickstart_click() { - this.navigationSelection("quickstart"); - } - - public explorer_click() { - this.navigationSelection("explorer"); - } -} - -var index = new Index(); -ko.applyBindings(index); diff --git a/src/Index.tsx b/src/Index.tsx new file mode 100644 index 000000000..d660eaed0 --- /dev/null +++ b/src/Index.tsx @@ -0,0 +1,66 @@ +import React, { useState } from "react"; +import ReactDOM from "react-dom"; +import Arrow from "../images/Arrow.svg"; +import CosmosDB_20170829 from "../images/CosmosDB_20170829.svg"; +import Explorer from "../images/Explorer.svg"; +import Feedback from "../images/Feedback.svg"; +import Quickstart from "../images/Quickstart.svg"; +import "../less/index.less"; + +const Index = (): JSX.Element => { + const [navigationSelection, setNavigationSelection] = useState("quickstart"); + + const quickstart_click = () => { + setNavigationSelection("quickstart"); + }; + + const explorer_click = () => { + setNavigationSelection("explorer"); + }; + + return ( + +
+
+ Azure Cosmos DB + + Create an Azure Cosmos DB account + + Azure Cosmos DB Emulator +
+
+ + + {navigationSelection === "quickstart" && ( + + )} + + {navigationSelection === "explorer" && ( + + )} +
+ ); +}; + +ReactDOM.render(, document.getElementById("root")); diff --git a/src/Localization/en/SqlX.json b/src/Localization/en/SqlX.json index 58f6e89a6..9c0ac667e 100644 --- a/src/Localization/en/SqlX.json +++ b/src/Localization/en/SqlX.json @@ -40,7 +40,7 @@ "CosmosD4Details": "General Purpose Cosmos Compute with 4 vCPUs, 16 GB Memory", "CosmosD8Details": "General Purpose Cosmos Compute with 8 vCPUs, 32 GB Memory", "CosmosD16Details": "General Purpose Cosmos Compute with 16 vCPUs, 64 GB Memory", - "Cost": "Cost", + "ApproximateCost": "Approximate Cost Per Hour", "CostText": "Hourly cost of the dedicated gateway resource depends on the SKU selection, number of instances per region, and number of regions.", "ConnectionString": "Connection String", "ConnectionStringText": "To use the dedicated gateway, use the connection string shown in ", diff --git a/src/SelfServe/SqlX/SqlX.rp.ts b/src/SelfServe/SqlX/SqlX.rp.ts index 365422c57..61080763e 100644 --- a/src/SelfServe/SqlX/SqlX.rp.ts +++ b/src/SelfServe/SqlX/SqlX.rp.ts @@ -4,7 +4,12 @@ import { armRequestWithoutPolling } from "../../Utils/arm/request"; import { selfServeTraceFailure, selfServeTraceStart, selfServeTraceSuccess } from "../SelfServeTelemetryProcessor"; import { RefreshResult } from "../SelfServeTypes"; import SqlX from "./SqlX"; -import { SqlxServiceResource, UpdateDedicatedGatewayRequestParameters } from "./SqlxTypes"; +import { + FetchPricesResponse, + RegionsResponse, + SqlxServiceResource, + UpdateDedicatedGatewayRequestParameters, +} from "./SqlxTypes"; const apiVersion = "2021-04-01-preview"; @@ -128,3 +133,67 @@ export const refreshDedicatedGatewayProvisioning = async (): Promise { + return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${name}`; +}; + +export const getReadRegions = async (): Promise> => { + try { + const readRegions = new Array(); + + const response = await armRequestWithoutPolling({ + host: configContext.ARM_ENDPOINT, + path: getGeneralPath(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name), + method: "GET", + apiVersion: "2021-04-01-preview", + }); + + if (response.result.location !== undefined) { + readRegions.push(response.result.location.replace(" ", "").toLowerCase()); + } else { + for (const location of response.result.locations) { + readRegions.push(location.locationName.replace(" ", "").toLowerCase()); + } + } + return readRegions; + } catch (err) { + return new Array(); + } +}; + +const getFetchPricesPathForRegion = (subscriptionId: string): string => { + return `/subscriptions/${subscriptionId}/providers/Microsoft.CostManagement/fetchPrices`; +}; + +export const getPriceMap = async (regions: Array): Promise>> => { + try { + const priceMap = new Map>(); + + for (const region of regions) { + const regionPriceMap = new Map(); + + const response = await armRequestWithoutPolling({ + host: configContext.ARM_ENDPOINT, + path: getFetchPricesPathForRegion(userContext.subscriptionId), + method: "POST", + apiVersion: "2020-01-01-preview", + queryParams: { + filter: + "armRegionName eq '" + + region + + "' and serviceFamily eq 'Databases' and productName eq 'Azure Cosmos DB Dedicated Gateway - General Purpose'", + }, + }); + + for (const item of response.result.Items) { + regionPriceMap.set(item.skuName, item.retailPrice); + } + priceMap.set(region, regionPriceMap); + } + + return priceMap; + } catch (err) { + return undefined; + } +}; diff --git a/src/SelfServe/SqlX/SqlX.tsx b/src/SelfServe/SqlX/SqlX.tsx index c6a431ede..ca1177fa3 100644 --- a/src/SelfServe/SqlX/SqlX.tsx +++ b/src/SelfServe/SqlX/SqlX.tsx @@ -16,11 +16,13 @@ import { BladeType, generateBladeLink } from "../SelfServeUtils"; import { deleteDedicatedGatewayResource, getCurrentProvisioningState, + getPriceMap, + getReadRegions, refreshDedicatedGatewayProvisioning, updateDedicatedGatewayResource, } from "./SqlX.rp"; -const costPerHourValue: Description = { +const costPerHourDefaultValue: Description = { textTKey: "CostText", type: DescriptionType.Text, link: { @@ -53,7 +55,10 @@ const CosmosD16s = "Cosmos.D16s"; const onSKUChange = (newValue: InputType, currentValues: Map): Map => { currentValues.set("sku", { value: newValue }); - currentValues.set("costPerHour", { value: costPerHourValue }); + currentValues.set("costPerHour", { + value: calculateCost(newValue as string, currentValues.get("instances").value as number), + }); + return currentValues; }; @@ -79,6 +84,11 @@ const onNumberOfInstancesChange = ( } else { currentValues.set("warningBanner", undefined); } + + currentValues.set("costPerHour", { + value: calculateCost(currentValues.get("sku").value as string, newValue as number), + }); + return currentValues; }; @@ -111,6 +121,11 @@ const onEnableDedicatedGatewayChange = ( } as Description, hidden: false, }); + + currentValues.set("costPerHour", { + value: calculateCost(baselineValues.get("sku").value as string, baselineValues.get("instances").value as number), + hidden: false, + }); } else { currentValues.set("warningBanner", { value: { @@ -122,6 +137,8 @@ const onEnableDedicatedGatewayChange = ( } as Description, hidden: false, }); + + currentValues.set("costPerHour", { value: costPerHourDefaultValue, hidden: true }); } const sku = currentValues.get("sku"); const instances = currentValues.get("instances"); @@ -137,7 +154,6 @@ const onEnableDedicatedGatewayChange = ( disabled: dedicatedGatewayOriginallyEnabled, }); - currentValues.set("costPerHour", { value: costPerHourValue, hidden: hideAttributes }); currentValues.set("connectionString", { value: connectionStringValue, hidden: !newValue || !dedicatedGatewayOriginallyEnabled, @@ -177,6 +193,40 @@ const NumberOfInstancesDropdownInfo: Info = { }, }; +const ApproximateCostDropDownInfo: Info = { + messageTKey: "CostText", + link: { + href: "https://aka.ms/cosmos-db-dedicated-gateway-pricing", + textTKey: "DedicatedGatewayPricing", + }, +}; + +let priceMap: Map>; +let regions: Array; + +const calculateCost = (skuName: string, instanceCount: number): Description => { + try { + let costPerHour = 0; + for (const region of regions) { + const incrementalCost = priceMap.get(region).get(skuName.replace("Cosmos.", "")); + if (incrementalCost === undefined) { + throw new Error("Value not found in map."); + } + costPerHour += incrementalCost; + } + + costPerHour *= instanceCount; + costPerHour = Math.round(costPerHour * 100) / 100; + + return { + textTKey: `${costPerHour} USD`, + type: DescriptionType.Text, + }; + } catch (err) { + return costPerHourDefaultValue; + } +}; + @IsDisplayable() @RefreshOptions({ retryIntervalInMs: 20000 }) export default class SqlX extends SelfServeBaseClass { @@ -274,12 +324,15 @@ export default class SqlX extends SelfServeBaseClass { hidden: true, }); + regions = await getReadRegions(); + priceMap = await getPriceMap(regions); + const response = await getCurrentProvisioningState(); if (response.status && response.status !== "Deleting") { defaults.set("enableDedicatedGateway", { value: true }); defaults.set("sku", { value: response.sku, disabled: true }); defaults.set("instances", { value: response.instances, disabled: false }); - defaults.set("costPerHour", { value: costPerHourValue }); + defaults.set("costPerHour", { value: calculateCost(response.sku, response.instances) }); defaults.set("connectionString", { value: connectionStringValue, hidden: false, @@ -338,8 +391,9 @@ export default class SqlX extends SelfServeBaseClass { }) instances: number; + @PropertyInfo(ApproximateCostDropDownInfo) @Values({ - labelTKey: "Cost", + labelTKey: "ApproximateCost", isDynamicDescription: true, }) costPerHour: string; diff --git a/src/SelfServe/SqlX/SqlxTypes.ts b/src/SelfServe/SqlX/SqlxTypes.ts index 70557f4f4..a150ccbb1 100644 --- a/src/SelfServe/SqlX/SqlxTypes.ts +++ b/src/SelfServe/SqlX/SqlxTypes.ts @@ -29,3 +29,23 @@ export type UpdateDedicatedGatewayRequestProperties = { instanceCount: number; serviceType: string; }; + +export type FetchPricesResponse = { + Items: Array; + NextPageLink: string | undefined; + Count: number; +}; + +export type PriceItem = { + retailPrice: number; + skuName: string; +}; + +export type RegionsResponse = { + locations: Array; + location: string; +}; + +export type RegionItem = { + locationName: string; +}; diff --git a/src/index.html b/src/index.html index e9672edd7..5332ae673 100644 --- a/src/index.html +++ b/src/index.html @@ -8,54 +8,7 @@ - -
-
- Azure Cosmos DB - - Create an Azure Cosmos DB account - - Azure Cosmos DB Emulator -
-
- - - - - - +
diff --git a/tsconfig.strict.json b/tsconfig.strict.json index 2ba4793f4..ca86d23ad 100644 --- a/tsconfig.strict.json +++ b/tsconfig.strict.json @@ -83,7 +83,6 @@ "./src/Explorer/Tree/AccessibleVerticalList.ts", "./src/GitHub/GitHubConnector.ts", "./src/HostedExplorerChildFrame.ts", - "./src/Index.ts", "./src/Platform/Hosted/Authorization.ts", "./src/Platform/Hosted/Components/MeControl.test.tsx", "./src/Platform/Hosted/Components/MeControl.tsx", diff --git a/webpack.config.js b/webpack.config.js index 12c980224..054b06984 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -198,7 +198,7 @@ module.exports = function (_env = {}, argv = {}) { mode: mode, entry: { main: "./src/Main.tsx", - index: "./src/Index.ts", + index: "./src/Index.tsx", quickstart: "./src/quickstart.ts", hostedExplorer: "./src/HostedExplorer.tsx", testExplorer: "./test/testExplorer/TestExplorer.ts",