mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-06-13 07:57:26 +01:00
Merge branch 'master' of https://github.com/Azure/cosmos-explorer into migrate/QueryTablesTab
This commit is contained in:
+3
-15
@@ -44,7 +44,6 @@ src/Definitions/png.d.ts
|
|||||||
src/Definitions/svg.d.ts
|
src/Definitions/svg.d.ts
|
||||||
src/Explorer/ComponentRegisterer.test.ts
|
src/Explorer/ComponentRegisterer.test.ts
|
||||||
src/Explorer/ComponentRegisterer.ts
|
src/Explorer/ComponentRegisterer.ts
|
||||||
src/Explorer/ContextMenuButtonFactory.ts
|
|
||||||
src/Explorer/Controls/CollapsiblePanel/CollapsiblePanelComponent.ts
|
src/Explorer/Controls/CollapsiblePanel/CollapsiblePanelComponent.ts
|
||||||
src/Explorer/Controls/DiffEditor/DiffEditorComponent.ts
|
src/Explorer/Controls/DiffEditor/DiffEditorComponent.ts
|
||||||
src/Explorer/Controls/DynamicList/DynamicList.test.ts
|
src/Explorer/Controls/DynamicList/DynamicList.test.ts
|
||||||
@@ -105,8 +104,6 @@ src/Explorer/Notebook/NotebookContainerClient.ts
|
|||||||
src/Explorer/Notebook/NotebookContentClient.ts
|
src/Explorer/Notebook/NotebookContentClient.ts
|
||||||
src/Explorer/Notebook/NotebookContentItem.ts
|
src/Explorer/Notebook/NotebookContentItem.ts
|
||||||
src/Explorer/Notebook/NotebookUtil.ts
|
src/Explorer/Notebook/NotebookUtil.ts
|
||||||
src/Explorer/OpenActions.test.ts
|
|
||||||
src/Explorer/OpenActions.ts
|
|
||||||
src/Explorer/OpenActionsStubs.ts
|
src/Explorer/OpenActionsStubs.ts
|
||||||
src/Explorer/Panes/AddDatabasePane.ts
|
src/Explorer/Panes/AddDatabasePane.ts
|
||||||
src/Explorer/Panes/AddDatabasePane.test.ts
|
src/Explorer/Panes/AddDatabasePane.test.ts
|
||||||
@@ -135,7 +132,6 @@ src/Explorer/Tables/QueryBuilder/ClauseGroupViewModel.ts
|
|||||||
src/Explorer/Tables/QueryBuilder/CustomTimestampHelper.ts
|
src/Explorer/Tables/QueryBuilder/CustomTimestampHelper.ts
|
||||||
src/Explorer/Tables/QueryBuilder/QueryBuilderViewModel.ts
|
src/Explorer/Tables/QueryBuilder/QueryBuilderViewModel.ts
|
||||||
src/Explorer/Tables/QueryBuilder/QueryClauseViewModel.ts
|
src/Explorer/Tables/QueryBuilder/QueryClauseViewModel.ts
|
||||||
src/Explorer/Tables/QueryBuilder/QueryViewModel.ts
|
|
||||||
src/Explorer/Tables/TableDataClient.ts
|
src/Explorer/Tables/TableDataClient.ts
|
||||||
src/Explorer/Tables/TableEntityProcessor.ts
|
src/Explorer/Tables/TableEntityProcessor.ts
|
||||||
src/Explorer/Tables/Utilities.ts
|
src/Explorer/Tables/Utilities.ts
|
||||||
@@ -145,14 +141,11 @@ src/Explorer/Tabs/DocumentsTab.test.ts
|
|||||||
src/Explorer/Tabs/DocumentsTab.ts
|
src/Explorer/Tabs/DocumentsTab.ts
|
||||||
src/Explorer/Tabs/GraphTab.ts
|
src/Explorer/Tabs/GraphTab.ts
|
||||||
src/Explorer/Tabs/MongoDocumentsTab.ts
|
src/Explorer/Tabs/MongoDocumentsTab.ts
|
||||||
src/Explorer/Tabs/MongoQueryTab.ts
|
# src/Explorer/Tabs/MongoQueryTab.ts
|
||||||
src/Explorer/Tabs/MongoShellTab.ts
|
# src/Explorer/Tabs/MongoShellTab.ts
|
||||||
src/Explorer/Tabs/NotebookV2Tab.ts
|
src/Explorer/Tabs/NotebookV2Tab.ts
|
||||||
src/Explorer/Tabs/QueryTab.test.ts
|
|
||||||
src/Explorer/Tabs/QueryTab.ts
|
|
||||||
src/Explorer/Tabs/QueryTablesTab.ts
|
|
||||||
src/Explorer/Tabs/ScriptTabBase.ts
|
src/Explorer/Tabs/ScriptTabBase.ts
|
||||||
src/Explorer/Tabs/StoredProcedureTab.ts
|
# src/Explorer/Tabs/StoredProcedureTab.ts
|
||||||
src/Explorer/Tabs/TabComponents.ts
|
src/Explorer/Tabs/TabComponents.ts
|
||||||
src/Explorer/Tabs/TabsBase.ts
|
src/Explorer/Tabs/TabsBase.ts
|
||||||
src/Explorer/Tabs/TriggerTab.ts
|
src/Explorer/Tabs/TriggerTab.ts
|
||||||
@@ -161,7 +154,6 @@ src/Explorer/Tree/AccessibleVerticalList.ts
|
|||||||
src/Explorer/Tree/Collection.test.ts
|
src/Explorer/Tree/Collection.test.ts
|
||||||
src/Explorer/Tree/Collection.ts
|
src/Explorer/Tree/Collection.ts
|
||||||
src/Explorer/Tree/ConflictId.ts
|
src/Explorer/Tree/ConflictId.ts
|
||||||
src/Explorer/Tree/Database.ts
|
|
||||||
src/Explorer/Tree/DocumentId.ts
|
src/Explorer/Tree/DocumentId.ts
|
||||||
src/Explorer/Tree/ObjectId.ts
|
src/Explorer/Tree/ObjectId.ts
|
||||||
src/Explorer/Tree/ResourceTokenCollection.ts
|
src/Explorer/Tree/ResourceTokenCollection.ts
|
||||||
@@ -205,9 +197,6 @@ src/ResourceProvider/IResourceProviderClient.test.ts
|
|||||||
src/ResourceProvider/IResourceProviderClient.ts
|
src/ResourceProvider/IResourceProviderClient.ts
|
||||||
src/ResourceProvider/ResourceProviderClient.ts
|
src/ResourceProvider/ResourceProviderClient.ts
|
||||||
src/ResourceProvider/ResourceProviderClientFactory.ts
|
src/ResourceProvider/ResourceProviderClientFactory.ts
|
||||||
src/RouteHandlers/RouteHandler.ts
|
|
||||||
src/RouteHandlers/TabRouteHandler.test.ts
|
|
||||||
src/RouteHandlers/TabRouteHandler.ts
|
|
||||||
src/Shared/Constants.ts
|
src/Shared/Constants.ts
|
||||||
src/Shared/DefaultExperienceUtility.test.ts
|
src/Shared/DefaultExperienceUtility.test.ts
|
||||||
src/Shared/DefaultExperienceUtility.ts
|
src/Shared/DefaultExperienceUtility.ts
|
||||||
@@ -263,7 +252,6 @@ src/Explorer/Graph/GraphExplorerComponent/EditorNodePropertiesComponent.test.tsx
|
|||||||
src/Explorer/Graph/GraphExplorerComponent/EditorNodePropertiesComponent.tsx
|
src/Explorer/Graph/GraphExplorerComponent/EditorNodePropertiesComponent.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
|
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
|
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/GraphExplorerAdapter.tsx
|
|
||||||
src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx
|
src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/LeftPaneComponent.tsx
|
src/Explorer/Graph/GraphExplorerComponent/LeftPaneComponent.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx
|
src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx
|
||||||
|
|||||||
+13
-27
@@ -724,45 +724,24 @@ execute-sproc-params-pane {
|
|||||||
|
|
||||||
.results-container,
|
.results-container,
|
||||||
.errors-container {
|
.errors-container {
|
||||||
padding: @MediumSpace 0px 0px @MediumSpace;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
.flex-display();
|
.flex-display();
|
||||||
.flex-direction();
|
.flex-direction();
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.toggles {
|
|
||||||
height: @ToggleHeight;
|
|
||||||
width: @ToggleWidth;
|
|
||||||
margin-left: @MediumSpace;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab {
|
|
||||||
margin-right: @MediumSpace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggleSwitch {
|
|
||||||
.toggleSwitch();
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectedToggle {
|
|
||||||
.selectedToggle();
|
|
||||||
}
|
|
||||||
|
|
||||||
.unselectedToggle {
|
|
||||||
.unselectedToggle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.enterInputParameters {
|
.enterInputParameters {
|
||||||
padding: @LargeSpace @MediumSpace;
|
padding: @LargeSpace @MediumSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div[role="tabpanel"] {
|
||||||
|
height: 100%;
|
||||||
|
padding-bottom: 50px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.errors-container {
|
.errors-container {
|
||||||
padding-left: (2 * @MediumSpace);
|
padding-left: (2 * @MediumSpace);
|
||||||
|
padding: @MediumSpace 0px 0px @MediumSpace;
|
||||||
.errors-header {
|
.errors-header {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: @DefaultFontSize;
|
font-size: @DefaultFontSize;
|
||||||
@@ -3089,3 +3068,10 @@ settings-pane {
|
|||||||
display: none;
|
display: none;
|
||||||
height: 0px;
|
height: 0px;
|
||||||
}
|
}
|
||||||
|
.spinner {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
background: white;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|||||||
@@ -201,3 +201,11 @@
|
|||||||
.migration:disabled {
|
.migration:disabled {
|
||||||
background-color: #ccc;
|
background-color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.trigger-field {
|
||||||
|
width: 40%;
|
||||||
|
margin-top: 10px
|
||||||
|
}
|
||||||
|
.trigger-form {
|
||||||
|
padding: 10px 30px 10px 30px;
|
||||||
|
}
|
||||||
Generated
+102
-30
@@ -3709,14 +3709,84 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@nteract/editor": {
|
"@nteract/editor": {
|
||||||
"version": "10.1.2",
|
"version": "10.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/@nteract/editor/-/editor-10.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@nteract/editor/-/editor-10.1.12.tgz",
|
||||||
"integrity": "sha512-Wtj0kJUSoBZsWUh82JGt6miqYS0jt0k+3SD3cnW9socayxp2KB0Qbqhh2NtrF9ysxVHWnQT8iUarJjpGIdNyng==",
|
"integrity": "sha512-bsUrCctukjWdpKNWQOQmhfxMCQ/SBVIO6+RkazI4y4dVeeP3KMP8nxfhzIbzTMNSkyynps/deZFjpDWqRhG+Dg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@nteract/messaging": "^7.0.10",
|
"@nteract/messaging": "^7.0.19",
|
||||||
"@nteract/outputs": "^3.0.9",
|
"@nteract/outputs": "^3.0.11",
|
||||||
"codemirror": "5.57.0",
|
"codemirror": "5.61.1",
|
||||||
"rxjs": "^6.3.3"
|
"rxjs": "^6.3.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@nteract/commutable": {
|
||||||
|
"version": "7.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nteract/commutable/-/commutable-7.4.5.tgz",
|
||||||
|
"integrity": "sha512-RYqyMvkFt/04GQ9T+hGYgr9/LEy0dAYJ2QKn930TFX004KjfBT6Tt8VSLFyHWkXqPwyJ0jKMCJwqLcGOI/atqg==",
|
||||||
|
"requires": {
|
||||||
|
"immutable": "^4.0.0-rc.12",
|
||||||
|
"uuid": "^8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@nteract/messaging": {
|
||||||
|
"version": "7.0.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nteract/messaging/-/messaging-7.0.19.tgz",
|
||||||
|
"integrity": "sha512-gRPMxJr741/BshrfCcPSbm5iVyRU2TKmAv9jeQzk0MZEGy+Y1A0REO+eptkt4Ma0OXlvDxON6JEDauk8+2xt4w==",
|
||||||
|
"requires": {
|
||||||
|
"@nteract/types": "^7.1.9",
|
||||||
|
"@types/uuid": "^8.0.0",
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
|
"rxjs": "^6.6.0",
|
||||||
|
"uuid": "^8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@nteract/outputs": {
|
||||||
|
"version": "3.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nteract/outputs/-/outputs-3.0.11.tgz",
|
||||||
|
"integrity": "sha512-LeT9ViBf+fTPSubZ9dMe7128kg0rl1jIG54V0n2GiU5RuYnUz21FU0IOaLMPUfFMO1VyVEOW5jDc3PAQx5/Kwg==",
|
||||||
|
"requires": {
|
||||||
|
"@nteract/markdown": "^4.5.2",
|
||||||
|
"@nteract/mathjax": "^4.0.11",
|
||||||
|
"ansi-to-react": "^6.0.5",
|
||||||
|
"react-json-tree": "^0.12.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@nteract/types": {
|
||||||
|
"version": "7.1.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nteract/types/-/types-7.1.9.tgz",
|
||||||
|
"integrity": "sha512-a7lGMWdjfz2QGlZbAiFHifU9Nhk9ntwg/iKUTMIMRPY1Wfs5UreHSMt+vZ8OY5HGjxicfHozBatGDKXeKXFHMQ==",
|
||||||
|
"requires": {
|
||||||
|
"@nteract/commutable": "^7.4.5",
|
||||||
|
"immutable": "^4.0.0-rc.12",
|
||||||
|
"rxjs": "^6.6.0",
|
||||||
|
"uuid": "^8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-base16-styling": {
|
||||||
|
"version": "0.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.7.0.tgz",
|
||||||
|
"integrity": "sha512-lTa/VSFdU6BOAj+FryOe7OTZ0OBP8GXPOnCS0QnZi7G3zhssWgIgwl0eUL77onXx/WqKPFndB3ZeC77QC/l4Dw==",
|
||||||
|
"requires": {
|
||||||
|
"base16": "^1.0.0",
|
||||||
|
"lodash.curry": "^4.1.1",
|
||||||
|
"lodash.flow": "^3.5.0",
|
||||||
|
"pure-color": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-json-tree": {
|
||||||
|
"version": "0.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.12.1.tgz",
|
||||||
|
"integrity": "sha512-j6fkRY7ha9XMv1HPVakRCsvyFwHGR5AZuwO8naBBeZXnZbbLor5tpcUxS/8XD01+D1v7ZN5p+7LU+9V1uyASiQ==",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"react-base16-styling": "^0.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@nteract/epics": {
|
"@nteract/epics": {
|
||||||
@@ -5650,6 +5720,15 @@
|
|||||||
"redux": "^4.0.0"
|
"redux": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/react-splitter-layout": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-splitter-layout/-/react-splitter-layout-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-NsKq32LdG11G/Uj+xo2QmC9S8YSe8JRtxkBhsBE7ODFs0zcnzNEqFAQirP0H7rPe2WFGiu+d/44xbHsew7QAJw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/react-table": {
|
"@types/react-table": {
|
||||||
"version": "6.8.7",
|
"version": "6.8.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-6.8.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-6.8.7.tgz",
|
||||||
@@ -8058,9 +8137,9 @@
|
|||||||
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
|
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
|
||||||
},
|
},
|
||||||
"codemirror": {
|
"codemirror": {
|
||||||
"version": "5.57.0",
|
"version": "5.61.1",
|
||||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.61.1.tgz",
|
||||||
"integrity": "sha512-WGc6UL7Hqt+8a6ZAsj/f1ApQl3NPvHY/UQSzG6fB6l4BjExgVdhFaxd7mRTw1UCiYe/6q86zHP+kfvBQcZGvUg=="
|
"integrity": "sha512-+D1NZjAucuzE93vJGbAaXzvoBHwp9nJZWWWF9utjv25+5AZUiah6CIlfb4ikG4MoDsFsCG8niiJH5++OO2LgIQ=="
|
||||||
},
|
},
|
||||||
"collapse-white-space": {
|
"collapse-white-space": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
@@ -17690,12 +17769,6 @@
|
|||||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"lodash": {
|
|
||||||
"version": "4.17.21",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"supports-color": {
|
"supports-color": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
@@ -18499,9 +18572,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.20",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||||
},
|
},
|
||||||
"lodash-es": {
|
"lodash-es": {
|
||||||
"version": "4.17.20",
|
"version": "4.17.20",
|
||||||
@@ -18728,9 +18801,9 @@
|
|||||||
"integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg=="
|
"integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg=="
|
||||||
},
|
},
|
||||||
"marked": {
|
"marked": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/marked/-/marked-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/marked/-/marked-2.0.6.tgz",
|
||||||
"integrity": "sha512-5otztIIcJfPc2qGTN8cVtOJEjNJZ0jwa46INMagrYfk0EvqtRuEHLsEe0LrFS0/q+ZRKT0+kXK7P2T1AN5lWRA==",
|
"integrity": "sha512-S2mYj0FzTQa0dLddssqwRVW4EOJOVJ355Xm2Vcbm+LU7GQRGWvwbO5K87OaPSOux2AwTSgtPPaXmc8sDPrhn2A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"martinez-polygon-clipping": {
|
"martinez-polygon-clipping": {
|
||||||
@@ -21635,6 +21708,11 @@
|
|||||||
"react-is": "^16.9.0"
|
"react-is": "^16.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-splitter-layout": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-splitter-layout/-/react-splitter-layout-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-SLqOjBOxRuizWUa83w6q5/u9cDWa9/yj9Iko9V9JFN8x+cqIXiDlUFWSx+icz3IIgvsN/oRIw3za5/32RjIwrA=="
|
||||||
|
},
|
||||||
"react-syntax-highlighter": {
|
"react-syntax-highlighter": {
|
||||||
"version": "12.2.1",
|
"version": "12.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-12.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-12.2.1.tgz",
|
||||||
@@ -24367,12 +24445,6 @@
|
|||||||
"universalify": "^2.0.0"
|
"universalify": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
|
||||||
"version": "4.17.21",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"universalify": {
|
"universalify": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||||
@@ -24388,9 +24460,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"version": "4.2.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz",
|
||||||
"integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==",
|
"integrity": "sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"typestyle": {
|
"typestyle": {
|
||||||
|
|||||||
+4
-2
@@ -22,7 +22,7 @@
|
|||||||
"@nteract/data-explorer": "8.0.3",
|
"@nteract/data-explorer": "8.0.3",
|
||||||
"@nteract/directory-listing": "2.0.6",
|
"@nteract/directory-listing": "2.0.6",
|
||||||
"@nteract/dropdown-menu": "1.0.1",
|
"@nteract/dropdown-menu": "1.0.1",
|
||||||
"@nteract/editor": "10.1.2",
|
"@nteract/editor": "10.1.12",
|
||||||
"@nteract/fixtures": "2.3.0",
|
"@nteract/fixtures": "2.3.0",
|
||||||
"@nteract/iron-icons": "1.0.0",
|
"@nteract/iron-icons": "1.0.0",
|
||||||
"@nteract/jupyter-widgets": "2.0.0",
|
"@nteract/jupyter-widgets": "2.0.0",
|
||||||
@@ -89,6 +89,7 @@
|
|||||||
"react-i18next": "11.8.5",
|
"react-i18next": "11.8.5",
|
||||||
"react-notification-system": "0.2.17",
|
"react-notification-system": "0.2.17",
|
||||||
"react-redux": "7.1.3",
|
"react-redux": "7.1.3",
|
||||||
|
"react-splitter-layout": "4.0.0",
|
||||||
"redux": "4.0.4",
|
"redux": "4.0.4",
|
||||||
"reflect-metadata": "0.1.13",
|
"reflect-metadata": "0.1.13",
|
||||||
"rx-jupyter": "5.5.12",
|
"rx-jupyter": "5.5.12",
|
||||||
@@ -123,6 +124,7 @@
|
|||||||
"@types/react-dom": "17.0.3",
|
"@types/react-dom": "17.0.3",
|
||||||
"@types/react-notification-system": "0.2.39",
|
"@types/react-notification-system": "0.2.39",
|
||||||
"@types/react-redux": "7.1.7",
|
"@types/react-redux": "7.1.7",
|
||||||
|
"@types/react-splitter-layout": "3.0.1",
|
||||||
"@types/sanitize-html": "1.27.2",
|
"@types/sanitize-html": "1.27.2",
|
||||||
"@types/sinon": "2.3.3",
|
"@types/sinon": "2.3.3",
|
||||||
"@types/styled-components": "5.1.1",
|
"@types/styled-components": "5.1.1",
|
||||||
@@ -172,7 +174,7 @@
|
|||||||
"tslint": "5.11.0",
|
"tslint": "5.11.0",
|
||||||
"tslint-microsoft-contrib": "6.0.0",
|
"tslint-microsoft-contrib": "6.0.0",
|
||||||
"typedoc": "0.20.36",
|
"typedoc": "0.20.36",
|
||||||
"typescript": "4.2.4",
|
"typescript": "4.3.4",
|
||||||
"url-loader": "1.1.1",
|
"url-loader": "1.1.1",
|
||||||
"wait-on": "4.0.2",
|
"wait-on": "4.0.2",
|
||||||
"webpack": "4.46.0",
|
"webpack": "4.46.0",
|
||||||
|
|||||||
@@ -158,16 +158,6 @@ export class DocumentsGridMetrics {
|
|||||||
public static DocumentEditorMaxWidthRatio: number = 0.4;
|
public static DocumentEditorMaxWidthRatio: number = 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExplorerMetrics {
|
|
||||||
public static SplitterMinWidth: number = 240;
|
|
||||||
public static SplitterMaxWidth: number = 400;
|
|
||||||
public static CollapsedResourceTreeWidth: number = 36;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SplitterMetrics {
|
|
||||||
public static CollapsedPositionLeft: number = ExplorerMetrics.CollapsedResourceTreeWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Areas {
|
export class Areas {
|
||||||
public static ResourceTree: string = "Resource Tree";
|
public static ResourceTree: string = "Resource Tree";
|
||||||
public static ContextualPane: string = "Contextual Pane";
|
public static ContextualPane: string = "Contextual Pane";
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { Collection } from "../Contracts/ViewModels";
|
|||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
import { updateUserContext } from "../UserContext";
|
import { updateUserContext } from "../UserContext";
|
||||||
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
|
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
|
||||||
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
|
|
||||||
|
|
||||||
const databaseId = "testDB";
|
const databaseId = "testDB";
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export function queryDocuments(
|
|||||||
headers: response.headers,
|
headers: response.headers,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
errorHandling(response, "querying documents", params);
|
await errorHandling(response, "querying documents", params);
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -153,11 +153,11 @@ export function readDocument(
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then(async (response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
return errorHandling(response, "reading document", params);
|
return await errorHandling(response, "reading document", params);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,11 +192,11 @@ export function createDocument(
|
|||||||
...authHeaders(),
|
...authHeaders(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then(async (response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
return errorHandling(response, "creating document", params);
|
return await errorHandling(response, "creating document", params);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,11 +238,11 @@ export function updateDocument(
|
|||||||
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
|
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then(async (response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
return errorHandling(response, "updating document", params);
|
return await errorHandling(response, "updating document", params);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,11 +278,11 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
|
|||||||
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
|
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then(async (response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return errorHandling(response, "deleting document", params);
|
return await errorHandling(response, "deleting document", params);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,11 +325,11 @@ export function createMongoCollectionWithProxy(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then((response) => {
|
.then(async (response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
return errorHandling(response, "creating collection", mongoParams);
|
return await errorHandling(response, "creating collection", mongoParams);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import Explorer from "../Explorer/Explorer";
|
import Explorer from "../Explorer/Explorer";
|
||||||
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
|
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
|
import { useDatabases } from "../Explorer/useDatabases";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||||
import * as QueryUtils from "../Utils/QueryUtils";
|
|
||||||
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
||||||
import { createCollection } from "./dataAccess/createCollection";
|
import { createCollection } from "./dataAccess/createCollection";
|
||||||
import { createDocument } from "./dataAccess/createDocument";
|
import { createDocument } from "./dataAccess/createDocument";
|
||||||
import { deleteDocument } from "./dataAccess/deleteDocument";
|
import { deleteDocument } from "./dataAccess/deleteDocument";
|
||||||
import { queryDocuments } from "./dataAccess/queryDocuments";
|
import { queryDocuments } from "./dataAccess/queryDocuments";
|
||||||
import { queryDocumentsPage } from "./dataAccess/queryDocumentsPage";
|
|
||||||
import { handleError } from "./ErrorHandlingUtils";
|
import { handleError } from "./ErrorHandlingUtils";
|
||||||
|
|
||||||
export class QueriesClient {
|
export class QueriesClient {
|
||||||
@@ -100,18 +98,14 @@ export class QueriesClient {
|
|||||||
|
|
||||||
const options: any = { enableCrossPartitionQuery: true };
|
const options: any = { enableCrossPartitionQuery: true };
|
||||||
const clearMessage = NotificationConsoleUtils.logConsoleProgress("Fetching saved queries");
|
const clearMessage = NotificationConsoleUtils.logConsoleProgress("Fetching saved queries");
|
||||||
const queryIterator: QueryIterator<ItemDefinition & Resource> = queryDocuments(
|
const results = await queryDocuments(
|
||||||
SavedQueries.DatabaseName,
|
SavedQueries.DatabaseName,
|
||||||
SavedQueries.CollectionName,
|
SavedQueries.CollectionName,
|
||||||
this.fetchQueriesQuery(),
|
this.fetchQueriesQuery(),
|
||||||
options
|
options
|
||||||
);
|
).fetchAll();
|
||||||
const fetchQueries = async (firstItemIndex: number): Promise<ViewModels.QueryResults> =>
|
|
||||||
await queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex);
|
let queries: DataModels.Query[] = _.map(results.resources, (document: DataModels.Query) => {
|
||||||
return QueryUtils.queryAllPages(fetchQueries)
|
|
||||||
.then(
|
|
||||||
(results: ViewModels.QueryResults) => {
|
|
||||||
let queries: DataModels.Query[] = _.map(results.documents, (document: DataModels.Query) => {
|
|
||||||
if (!document) {
|
if (!document) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -131,14 +125,8 @@ export class QueriesClient {
|
|||||||
});
|
});
|
||||||
queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery);
|
queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery);
|
||||||
NotificationConsoleUtils.logConsoleInfo("Successfully fetched saved queries");
|
NotificationConsoleUtils.logConsoleInfo("Successfully fetched saved queries");
|
||||||
return Promise.resolve(queries);
|
clearMessage();
|
||||||
},
|
return queries;
|
||||||
(error: any) => {
|
|
||||||
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => clearMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteQuery(query: DataModels.Query): Promise<void> {
|
public async deleteQuery(query: DataModels.Query): Promise<void> {
|
||||||
@@ -189,7 +177,7 @@ export class QueriesClient {
|
|||||||
|
|
||||||
private findQueriesCollection(): ViewModels.Collection {
|
private findQueriesCollection(): ViewModels.Collection {
|
||||||
const queriesDatabase: ViewModels.Database = _.find(
|
const queriesDatabase: ViewModels.Database = _.find(
|
||||||
this.container.databases(),
|
useDatabases.getState().databases,
|
||||||
(database: ViewModels.Database) => database.id() === SavedQueries.DatabaseName
|
(database: ViewModels.Database) => database.id() === SavedQueries.DatabaseName
|
||||||
);
|
);
|
||||||
if (!queriesDatabase) {
|
if (!queriesDatabase) {
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
|
|
||||||
import { SplitterMetrics } from "./Constants";
|
|
||||||
|
|
||||||
export enum SplitterDirection {
|
export enum SplitterDirection {
|
||||||
Horizontal = "horizontal",
|
Horizontal = "horizontal",
|
||||||
Vertical = "vertical",
|
Vertical = "vertical",
|
||||||
@@ -28,14 +24,12 @@ export class Splitter {
|
|||||||
public lastX!: number;
|
public lastX!: number;
|
||||||
public lastWidth!: number;
|
public lastWidth!: number;
|
||||||
|
|
||||||
private isCollapsed: ko.Observable<boolean>;
|
|
||||||
private bounds: SplitterBounds;
|
private bounds: SplitterBounds;
|
||||||
private direction: SplitterDirection;
|
private direction: SplitterDirection;
|
||||||
|
|
||||||
constructor(options: SplitterOptions) {
|
constructor(options: SplitterOptions) {
|
||||||
this.splitterId = options.splitterId;
|
this.splitterId = options.splitterId;
|
||||||
this.leftSideId = options.leftId;
|
this.leftSideId = options.leftId;
|
||||||
this.isCollapsed = ko.observable<boolean>(false);
|
|
||||||
this.bounds = options.bounds;
|
this.bounds = options.bounds;
|
||||||
this.direction = options.direction;
|
this.direction = options.direction;
|
||||||
this.initialize();
|
this.initialize();
|
||||||
@@ -83,23 +77,4 @@ export class Splitter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private onResizeStop: JQueryUI.ResizableEvent = () => $("iframe").css("pointer-events", "auto");
|
private onResizeStop: JQueryUI.ResizableEvent = () => $("iframe").css("pointer-events", "auto");
|
||||||
|
|
||||||
public collapseLeft() {
|
|
||||||
this.lastX = $(this.splitter).position().left;
|
|
||||||
this.lastWidth = $(this.leftSide).width();
|
|
||||||
$(this.splitter).css("left", SplitterMetrics.CollapsedPositionLeft);
|
|
||||||
$(this.leftSide).css("width", "");
|
|
||||||
$(this.leftSide).resizable("option", "disabled", true).removeClass("ui-resizable-disabled"); // remove class so splitter is visible
|
|
||||||
$(this.splitter).removeClass("ui-resizable-e");
|
|
||||||
this.isCollapsed(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public expandLeft() {
|
|
||||||
$(this.splitter).addClass("ui-resizable-e");
|
|
||||||
$(this.leftSide).css("width", this.lastWidth);
|
|
||||||
$(this.splitter).css("left", this.lastX);
|
|
||||||
$(this.splitter).css("left", ""); // this ensures the splitter's position is not fixed and enables movement during resizing
|
|
||||||
$(this.leftSide).resizable("enable");
|
|
||||||
this.isCollapsed(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,6 +120,14 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
|
|||||||
const armAPIVersion = params.get("armAPIVersion") || "";
|
const armAPIVersion = params.get("armAPIVersion") || "";
|
||||||
updateConfigContext({ armAPIVersion });
|
updateConfigContext({ armAPIVersion });
|
||||||
}
|
}
|
||||||
|
if (params.has("armEndpoint")) {
|
||||||
|
const ARM_ENDPOINT = params.get("armEndpoint") || "";
|
||||||
|
updateConfigContext({ ARM_ENDPOINT });
|
||||||
|
}
|
||||||
|
if (params.has("aadEndpoint")) {
|
||||||
|
const AAD_ENDPOINT = params.get("aadEndpoint") || "";
|
||||||
|
updateConfigContext({ AAD_ENDPOINT });
|
||||||
|
}
|
||||||
if (params.has("platform")) {
|
if (params.has("platform")) {
|
||||||
const platform = params.get("platform");
|
const platform = params.get("platform");
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
UserDefinedFunctionDefinition,
|
UserDefinedFunctionDefinition,
|
||||||
} from "@azure/cosmos";
|
} from "@azure/cosmos";
|
||||||
import Explorer from "../Explorer/Explorer";
|
import Explorer from "../Explorer/Explorer";
|
||||||
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/ConsoleData";
|
||||||
import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient";
|
import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient";
|
||||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
import ConflictId from "../Explorer/Tree/ConflictId";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
@@ -89,7 +89,6 @@ export interface Database extends TreeNode {
|
|||||||
|
|
||||||
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
|
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
|
||||||
|
|
||||||
selectDatabase(): void;
|
|
||||||
expandDatabase(): Promise<void>;
|
expandDatabase(): Promise<void>;
|
||||||
collapseDatabase(): void;
|
collapseDatabase(): void;
|
||||||
|
|
||||||
@@ -275,7 +274,6 @@ export interface TabOptions {
|
|||||||
tabKind: CollectionTabKind;
|
tabKind: CollectionTabKind;
|
||||||
title: string;
|
title: string;
|
||||||
tabPath: string;
|
tabPath: string;
|
||||||
hashLocation: string;
|
|
||||||
isTabsContentExpanded?: ko.Observable<boolean>;
|
isTabsContentExpanded?: ko.Observable<boolean>;
|
||||||
onLoadStartKey?: number;
|
onLoadStartKey?: number;
|
||||||
|
|
||||||
@@ -286,6 +284,7 @@ export interface TabOptions {
|
|||||||
rid?: string;
|
rid?: string;
|
||||||
node?: TreeNode;
|
node?: TreeNode;
|
||||||
theme?: string;
|
theme?: string;
|
||||||
|
index?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DocumentsTabOptions extends TabOptions {
|
export interface DocumentsTabOptions extends TabOptions {
|
||||||
|
|||||||
@@ -1,172 +0,0 @@
|
|||||||
import AddCollectionIcon from "../../images/AddCollection.svg";
|
|
||||||
import AddSqlQueryIcon from "../../images/AddSqlQuery_16x16.svg";
|
|
||||||
import AddStoredProcedureIcon from "../../images/AddStoredProcedure.svg";
|
|
||||||
import AddTriggerIcon from "../../images/AddTrigger.svg";
|
|
||||||
import AddUdfIcon from "../../images/AddUdf.svg";
|
|
||||||
import DeleteCollectionIcon from "../../images/DeleteCollection.svg";
|
|
||||||
import DeleteDatabaseIcon from "../../images/DeleteDatabase.svg";
|
|
||||||
import DeleteSprocIcon from "../../images/DeleteSproc.svg";
|
|
||||||
import DeleteTriggerIcon from "../../images/DeleteTrigger.svg";
|
|
||||||
import DeleteUDFIcon from "../../images/DeleteUDF.svg";
|
|
||||||
import HostedTerminalIcon from "../../images/Hosted-Terminal.svg";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
|
||||||
import { userContext } from "../UserContext";
|
|
||||||
import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils";
|
|
||||||
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
|
|
||||||
import Explorer from "./Explorer";
|
|
||||||
import StoredProcedure from "./Tree/StoredProcedure";
|
|
||||||
import Trigger from "./Tree/Trigger";
|
|
||||||
import UserDefinedFunction from "./Tree/UserDefinedFunction";
|
|
||||||
export interface CollectionContextMenuButtonParams {
|
|
||||||
databaseId: string;
|
|
||||||
collectionId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DatabaseContextMenuButtonParams {
|
|
||||||
databaseId: string;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* New resource tree (in ReactJS)
|
|
||||||
*/
|
|
||||||
export class ResourceTreeContextMenuButtonFactory {
|
|
||||||
public static createDatabaseContextMenu(container: Explorer, databaseId: string): TreeNodeMenuItem[] {
|
|
||||||
const items: TreeNodeMenuItem[] = [
|
|
||||||
{
|
|
||||||
iconSrc: AddCollectionIcon,
|
|
||||||
onClick: () => container.onNewCollectionClicked(databaseId),
|
|
||||||
label: `New ${getCollectionName()}`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (userContext.apiType !== "Tables") {
|
|
||||||
items.push({
|
|
||||||
iconSrc: DeleteDatabaseIcon,
|
|
||||||
onClick: () => container.openDeleteDatabaseConfirmationPane(),
|
|
||||||
label: `Delete ${getDatabaseName()}`,
|
|
||||||
styleClass: "deleteDatabaseMenuItem",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static createCollectionContextMenuButton(
|
|
||||||
container: Explorer,
|
|
||||||
selectedCollection: ViewModels.Collection
|
|
||||||
): TreeNodeMenuItem[] {
|
|
||||||
const items: TreeNodeMenuItem[] = [];
|
|
||||||
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
|
|
||||||
items.push({
|
|
||||||
iconSrc: AddSqlQueryIcon,
|
|
||||||
onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null),
|
|
||||||
label: "New SQL Query",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userContext.apiType === "Mongo") {
|
|
||||||
items.push({
|
|
||||||
iconSrc: AddSqlQueryIcon,
|
|
||||||
onClick: () => selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null),
|
|
||||||
label: "New Query",
|
|
||||||
});
|
|
||||||
|
|
||||||
items.push({
|
|
||||||
iconSrc: HostedTerminalIcon,
|
|
||||||
onClick: () => {
|
|
||||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
|
||||||
if (container.isShellEnabled()) {
|
|
||||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
|
||||||
} else {
|
|
||||||
selectedCollection && selectedCollection.onNewMongoShellClick();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
label: container.isShellEnabled() ? "Open Mongo Shell" : "New Shell",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
|
|
||||||
items.push({
|
|
||||||
iconSrc: AddStoredProcedureIcon,
|
|
||||||
onClick: () => {
|
|
||||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
|
||||||
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null);
|
|
||||||
},
|
|
||||||
label: "New Stored Procedure",
|
|
||||||
});
|
|
||||||
|
|
||||||
items.push({
|
|
||||||
iconSrc: AddUdfIcon,
|
|
||||||
onClick: () => {
|
|
||||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
|
||||||
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, null);
|
|
||||||
},
|
|
||||||
label: "New UDF",
|
|
||||||
});
|
|
||||||
|
|
||||||
items.push({
|
|
||||||
iconSrc: AddTriggerIcon,
|
|
||||||
onClick: () => {
|
|
||||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
|
||||||
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, null);
|
|
||||||
},
|
|
||||||
label: "New Trigger",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
items.push({
|
|
||||||
iconSrc: DeleteCollectionIcon,
|
|
||||||
onClick: () => container.openDeleteCollectionConfirmationPane(),
|
|
||||||
label: `Delete ${getCollectionName()}`,
|
|
||||||
styleClass: "deleteCollectionMenuItem",
|
|
||||||
});
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static createStoreProcedureContextMenuItems(
|
|
||||||
container: Explorer,
|
|
||||||
storedProcedure: StoredProcedure
|
|
||||||
): TreeNodeMenuItem[] {
|
|
||||||
if (userContext.apiType === "Cassandra") {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
iconSrc: DeleteSprocIcon,
|
|
||||||
onClick: () => storedProcedure.delete(),
|
|
||||||
label: "Delete Store Procedure",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static createTriggerContextMenuItems(container: Explorer, trigger: Trigger): TreeNodeMenuItem[] {
|
|
||||||
if (userContext.apiType === "Cassandra") {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
iconSrc: DeleteTriggerIcon,
|
|
||||||
onClick: () => trigger.delete(),
|
|
||||||
label: "Delete Trigger",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static createUserDefinedFunctionContextMenuItems(
|
|
||||||
container: Explorer,
|
|
||||||
userDefinedFunction: UserDefinedFunction
|
|
||||||
): TreeNodeMenuItem[] {
|
|
||||||
if (userContext.apiType === "Cassandra") {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
iconSrc: DeleteUDFIcon,
|
|
||||||
onClick: () => userDefinedFunction.delete(),
|
|
||||||
label: "Delete User Defined Function",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
import React from "react";
|
||||||
|
import AddCollectionIcon from "../../images/AddCollection.svg";
|
||||||
|
import AddSqlQueryIcon from "../../images/AddSqlQuery_16x16.svg";
|
||||||
|
import AddStoredProcedureIcon from "../../images/AddStoredProcedure.svg";
|
||||||
|
import AddTriggerIcon from "../../images/AddTrigger.svg";
|
||||||
|
import AddUdfIcon from "../../images/AddUdf.svg";
|
||||||
|
import DeleteCollectionIcon from "../../images/DeleteCollection.svg";
|
||||||
|
import DeleteDatabaseIcon from "../../images/DeleteDatabase.svg";
|
||||||
|
import DeleteSprocIcon from "../../images/DeleteSproc.svg";
|
||||||
|
import DeleteTriggerIcon from "../../images/DeleteTrigger.svg";
|
||||||
|
import DeleteUDFIcon from "../../images/DeleteUDF.svg";
|
||||||
|
import HostedTerminalIcon from "../../images/Hosted-Terminal.svg";
|
||||||
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
|
import { useSidePanel } from "../hooks/useSidePanel";
|
||||||
|
import { userContext } from "../UserContext";
|
||||||
|
import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils";
|
||||||
|
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
|
||||||
|
import Explorer from "./Explorer";
|
||||||
|
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
|
||||||
|
import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel";
|
||||||
|
import StoredProcedure from "./Tree/StoredProcedure";
|
||||||
|
import Trigger from "./Tree/Trigger";
|
||||||
|
import UserDefinedFunction from "./Tree/UserDefinedFunction";
|
||||||
|
import { useSelectedNode } from "./useSelectedNode";
|
||||||
|
|
||||||
|
export interface CollectionContextMenuButtonParams {
|
||||||
|
databaseId: string;
|
||||||
|
collectionId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DatabaseContextMenuButtonParams {
|
||||||
|
databaseId: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* New resource tree (in ReactJS)
|
||||||
|
*/
|
||||||
|
export const createDatabaseContextMenu = (container: Explorer, databaseId: string): TreeNodeMenuItem[] => {
|
||||||
|
const items: TreeNodeMenuItem[] = [
|
||||||
|
{
|
||||||
|
iconSrc: AddCollectionIcon,
|
||||||
|
onClick: () => container.onNewCollectionClicked(databaseId),
|
||||||
|
label: `New ${getCollectionName()}`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (userContext.apiType !== "Tables") {
|
||||||
|
items.push({
|
||||||
|
iconSrc: DeleteDatabaseIcon,
|
||||||
|
onClick: () =>
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel("Delete " + getDatabaseName(), <DeleteDatabaseConfirmationPanel explorer={container} />),
|
||||||
|
label: `Delete ${getDatabaseName()}`,
|
||||||
|
styleClass: "deleteDatabaseMenuItem",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createCollectionContextMenuButton = (
|
||||||
|
container: Explorer,
|
||||||
|
selectedCollection: ViewModels.Collection
|
||||||
|
): TreeNodeMenuItem[] => {
|
||||||
|
const items: TreeNodeMenuItem[] = [];
|
||||||
|
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
|
||||||
|
items.push({
|
||||||
|
iconSrc: AddSqlQueryIcon,
|
||||||
|
onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, undefined),
|
||||||
|
label: "New SQL Query",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userContext.apiType === "Mongo") {
|
||||||
|
items.push({
|
||||||
|
iconSrc: AddSqlQueryIcon,
|
||||||
|
onClick: () => selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, undefined),
|
||||||
|
label: "New Query",
|
||||||
|
});
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
iconSrc: HostedTerminalIcon,
|
||||||
|
onClick: () => {
|
||||||
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
|
if (container.isShellEnabled()) {
|
||||||
|
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||||
|
} else {
|
||||||
|
selectedCollection && selectedCollection.onNewMongoShellClick();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label: container.isShellEnabled() ? "Open Mongo Shell" : "New Shell",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
|
||||||
|
items.push({
|
||||||
|
iconSrc: AddStoredProcedureIcon,
|
||||||
|
onClick: () => {
|
||||||
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
|
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, undefined);
|
||||||
|
},
|
||||||
|
label: "New Stored Procedure",
|
||||||
|
});
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
iconSrc: AddUdfIcon,
|
||||||
|
onClick: () => {
|
||||||
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
|
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, undefined);
|
||||||
|
},
|
||||||
|
label: "New UDF",
|
||||||
|
});
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
iconSrc: AddTriggerIcon,
|
||||||
|
onClick: () => {
|
||||||
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
|
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, undefined);
|
||||||
|
},
|
||||||
|
label: "New Trigger",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
iconSrc: DeleteCollectionIcon,
|
||||||
|
onClick: () =>
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel("Delete " + getCollectionName(), <DeleteCollectionConfirmationPane explorer={container} />),
|
||||||
|
label: `Delete ${getCollectionName()}`,
|
||||||
|
styleClass: "deleteCollectionMenuItem",
|
||||||
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createStoreProcedureContextMenuItems = (
|
||||||
|
container: Explorer,
|
||||||
|
storedProcedure: StoredProcedure
|
||||||
|
): TreeNodeMenuItem[] => {
|
||||||
|
if (userContext.apiType === "Cassandra") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
iconSrc: DeleteSprocIcon,
|
||||||
|
onClick: () => storedProcedure.delete(),
|
||||||
|
label: "Delete Store Procedure",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createTriggerContextMenuItems = (container: Explorer, trigger: Trigger): TreeNodeMenuItem[] => {
|
||||||
|
if (userContext.apiType === "Cassandra") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
iconSrc: DeleteTriggerIcon,
|
||||||
|
onClick: () => trigger.delete(),
|
||||||
|
label: "Delete Trigger",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createUserDefinedFunctionContextMenuItems = (
|
||||||
|
container: Explorer,
|
||||||
|
userDefinedFunction: UserDefinedFunction
|
||||||
|
): TreeNodeMenuItem[] => {
|
||||||
|
if (userContext.apiType === "Cassandra") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
iconSrc: DeleteUDFIcon,
|
||||||
|
onClick: () => userDefinedFunction.delete(),
|
||||||
|
label: "Delete User Defined Function",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
|
import { Spinner, SpinnerSize } from "@fluentui/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { loadMonaco, monaco } from "../../LazyMonaco";
|
import { loadMonaco, monaco } from "../../LazyMonaco";
|
||||||
|
// import "./EditorReact.less";
|
||||||
|
|
||||||
|
interface EditorReactStates {
|
||||||
|
showEditor: boolean;
|
||||||
|
}
|
||||||
export interface EditorReactProps {
|
export interface EditorReactProps {
|
||||||
language: string;
|
language: string;
|
||||||
content: string;
|
content: string;
|
||||||
@@ -12,22 +17,26 @@ export interface EditorReactProps {
|
|||||||
theme?: string; // Monaco editor theme
|
theme?: string; // Monaco editor theme
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EditorReact extends React.Component<EditorReactProps> {
|
export class EditorReact extends React.Component<EditorReactProps, EditorReactStates> {
|
||||||
private rootNode: HTMLElement;
|
private rootNode: HTMLElement;
|
||||||
private editor: monaco.editor.IStandaloneCodeEditor;
|
private editor: monaco.editor.IStandaloneCodeEditor;
|
||||||
private selectionListener: monaco.IDisposable;
|
private selectionListener: monaco.IDisposable;
|
||||||
|
|
||||||
public constructor(props: EditorReactProps) {
|
public constructor(props: EditorReactProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
showEditor: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount(): void {
|
public componentDidMount(): void {
|
||||||
this.createEditor(this.configureEditor.bind(this));
|
this.createEditor(this.configureEditor.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public shouldComponentUpdate(): boolean {
|
public componentDidUpdate(previous: EditorReactProps) {
|
||||||
// Prevents component re-rendering
|
if (this.props.content !== previous.content) {
|
||||||
return false;
|
this.editor.setValue(this.props.content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillUnmount(): void {
|
public componentWillUnmount(): void {
|
||||||
@@ -35,7 +44,12 @@ export class EditorReact extends React.Component<EditorReactProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
return <div className="jsonEditor" ref={(elt: HTMLElement) => this.setRef(elt)} />;
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{!this.state.showEditor && <Spinner size={SpinnerSize.large} className="spinner" />}
|
||||||
|
<div className="jsonEditor" ref={(elt: HTMLElement) => this.setRef(elt)} />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
|
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
|
||||||
@@ -76,6 +90,12 @@ export class EditorReact extends React.Component<EditorReactProps> {
|
|||||||
this.rootNode.innerHTML = "";
|
this.rootNode.innerHTML = "";
|
||||||
const monaco = await loadMonaco();
|
const monaco = await loadMonaco();
|
||||||
createCallback(monaco.editor.create(this.rootNode, options));
|
createCallback(monaco.editor.create(this.rootNode, options));
|
||||||
|
|
||||||
|
if (this.rootNode.innerHTML) {
|
||||||
|
this.setState({
|
||||||
|
showEditor: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setRef(element: HTMLElement): void {
|
private setRef(element: HTMLElement): void {
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import ko from "knockout";
|
|
||||||
import * as React from "react";
|
|
||||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
|
||||||
import { GitHubReposComponent, GitHubReposComponentProps } from "./GitHubReposComponent";
|
|
||||||
|
|
||||||
export class GitHubReposComponentAdapter implements ReactAdapter {
|
|
||||||
public parameters: ko.Observable<number>;
|
|
||||||
|
|
||||||
constructor(private props: GitHubReposComponentProps) {
|
|
||||||
this.parameters = ko.observable<number>(Date.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
|
||||||
return <GitHubReposComponent {...this.props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
public triggerRender(): void {
|
|
||||||
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -55,7 +55,7 @@ export class NotebookViewerComponent
|
|||||||
databaseAccountName: undefined,
|
databaseAccountName: undefined,
|
||||||
defaultExperience: "NotebookViewer",
|
defaultExperience: "NotebookViewer",
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
cellEditorType: "monaco",
|
cellEditorType: "codemirror",
|
||||||
autoSaveInterval: 365 * 24 * 3600 * 1000, // There is no way to turn off auto-save, set to 1 year
|
autoSaveInterval: 365 * 24 * 3600 * 1000, // There is no way to turn off auto-save, set to 1 year
|
||||||
contentProvider: contents.JupyterContentProvider, // NotebookViewer only knows how to talk to Jupyter contents API
|
contentProvider: contents.JupyterContentProvider, // NotebookViewer only knows how to talk to Jupyter contents API
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ describe("SettingsComponent", () => {
|
|||||||
title: "Scale & Settings",
|
title: "Scale & Settings",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
node: undefined,
|
node: undefined,
|
||||||
hashLocation: "settings",
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -127,7 +126,6 @@ describe("SettingsComponent", () => {
|
|||||||
isDatabaseExpanded: undefined,
|
isDatabaseExpanded: undefined,
|
||||||
isDatabaseShared: ko.computed(() => true),
|
isDatabaseShared: ko.computed(() => true),
|
||||||
selectedSubnodeKind: undefined,
|
selectedSubnodeKind: undefined,
|
||||||
selectDatabase: undefined,
|
|
||||||
expandDatabase: undefined,
|
expandDatabase: undefined,
|
||||||
collapseDatabase: undefined,
|
collapseDatabase: undefined,
|
||||||
loadCollections: undefined,
|
loadCollections: undefined,
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import { trace, traceFailure, traceStart, traceSuccess } from "../../../Shared/T
|
|||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
|
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
|
||||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||||
|
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../../Explorer";
|
|
||||||
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
|
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
|
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
|
||||||
import "./SettingsComponent.less";
|
import "./SettingsComponent.less";
|
||||||
@@ -110,6 +110,7 @@ export interface SettingsComponentState {
|
|||||||
|
|
||||||
initialNotification: DataModels.Notification;
|
initialNotification: DataModels.Notification;
|
||||||
selectedTab: SettingsV2TabTypes;
|
selectedTab: SettingsV2TabTypes;
|
||||||
|
offerLoaded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> {
|
export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> {
|
||||||
@@ -122,7 +123,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
private collection: ViewModels.Collection;
|
private collection: ViewModels.Collection;
|
||||||
private database: ViewModels.Database;
|
private database: ViewModels.Database;
|
||||||
private offer: DataModels.Offer;
|
private offer: DataModels.Offer;
|
||||||
private container: Explorer;
|
|
||||||
private changeFeedPolicyVisible: boolean;
|
private changeFeedPolicyVisible: boolean;
|
||||||
private isFixedContainer: boolean;
|
private isFixedContainer: boolean;
|
||||||
private shouldShowIndexingPolicyEditor: boolean;
|
private shouldShowIndexingPolicyEditor: boolean;
|
||||||
@@ -134,7 +134,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.isCollectionSettingsTab = this.props.settingsTab.tabKind === ViewModels.CollectionTabKind.CollectionSettingsV2;
|
this.isCollectionSettingsTab = this.props.settingsTab.tabKind === ViewModels.CollectionTabKind.CollectionSettingsV2;
|
||||||
if (this.isCollectionSettingsTab) {
|
if (this.isCollectionSettingsTab) {
|
||||||
this.collection = this.props.settingsTab.collection as ViewModels.Collection;
|
this.collection = this.props.settingsTab.collection as ViewModels.Collection;
|
||||||
this.container = this.collection?.container;
|
|
||||||
this.offer = this.collection?.offer();
|
this.offer = this.collection?.offer();
|
||||||
this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl();
|
this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl();
|
||||||
this.shouldShowIndexingPolicyEditor = userContext.apiType !== "Cassandra" && userContext.apiType !== "Mongo";
|
this.shouldShowIndexingPolicyEditor = userContext.apiType !== "Cassandra" && userContext.apiType !== "Mongo";
|
||||||
@@ -146,7 +145,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
userContext.apiType === "Mongo" && (!this.collection?.partitionKey || this.collection?.partitionKey.systemKey);
|
userContext.apiType === "Mongo" && (!this.collection?.partitionKey || this.collection?.partitionKey.systemKey);
|
||||||
} else {
|
} else {
|
||||||
this.database = this.props.settingsTab.database;
|
this.database = this.props.settingsTab.database;
|
||||||
this.container = this.database?.container;
|
|
||||||
this.offer = this.database?.offer();
|
this.offer = this.database?.offer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,6 +195,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
|
|
||||||
initialNotification: undefined,
|
initialNotification: undefined,
|
||||||
selectedTab: SettingsV2TabTypes.ScaleTab,
|
selectedTab: SettingsV2TabTypes.ScaleTab,
|
||||||
|
offerLoaded: !!this.offer,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.saveSettingsButton = {
|
this.saveSettingsButton = {
|
||||||
@@ -218,6 +217,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
if (this.isCollectionSettingsTab) {
|
if (this.isCollectionSettingsTab) {
|
||||||
this.refreshIndexTransformationProgress();
|
this.refreshIndexTransformationProgress();
|
||||||
this.loadMongoIndexes();
|
this.loadMongoIndexes();
|
||||||
|
this.loadCollectionOffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setAutoPilotStates();
|
this.setAutoPilotStates();
|
||||||
@@ -294,7 +294,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.state.wasAutopilotOriginallySet !== this.state.isAutoPilotSelected;
|
this.state.wasAutopilotOriginallySet !== this.state.isAutoPilotSelected;
|
||||||
|
|
||||||
public shouldShowKeyspaceSharedThroughputMessage = (): boolean =>
|
public shouldShowKeyspaceSharedThroughputMessage = (): boolean =>
|
||||||
this.container && userContext.apiType === "Cassandra" && hasDatabaseSharedThroughput(this.collection);
|
userContext.apiType === "Cassandra" && hasDatabaseSharedThroughput(this.collection);
|
||||||
|
|
||||||
public hasConflictResolution = (): boolean =>
|
public hasConflictResolution = (): boolean =>
|
||||||
userContext?.databaseAccount?.properties?.enableMultipleWriteLocations &&
|
userContext?.databaseAccount?.properties?.enableMultipleWriteLocations &&
|
||||||
@@ -372,6 +372,34 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private async loadCollectionOffer() {
|
||||||
|
try {
|
||||||
|
this.props.settingsTab.isExecuting(true);
|
||||||
|
await this.collection.loadOffer();
|
||||||
|
this.props.settingsTab.tabTitle(this.collection.offer() ? "Settings" : "Scale & Settings");
|
||||||
|
this.setState({ offerLoaded: true });
|
||||||
|
} catch (error) {
|
||||||
|
this.props.settingsTab.isExecutionError(true);
|
||||||
|
const errorMessage = getErrorMessage(error);
|
||||||
|
traceFailure(
|
||||||
|
Action.Tab,
|
||||||
|
{
|
||||||
|
databaseName: this.collection.databaseId,
|
||||||
|
collectionName: this.collection.id(),
|
||||||
|
|
||||||
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
|
tabTitle: this.props.settingsTab.tabTitle,
|
||||||
|
error: errorMessage,
|
||||||
|
errorStack: getErrorStack(error),
|
||||||
|
},
|
||||||
|
this.props.settingsTab.onLoadStartKey
|
||||||
|
);
|
||||||
|
logConsoleError(`Error while fetching container settings for container ${this.collection.id()}: ${errorMessage}`);
|
||||||
|
} finally {
|
||||||
|
this.props.settingsTab.isExecuting(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private getMongoIndexesToSave = (): MongoIndex[] => {
|
private getMongoIndexesToSave = (): MongoIndex[] => {
|
||||||
let finalIndexes: MongoIndex[] = [];
|
let finalIndexes: MongoIndex[] = [];
|
||||||
this.state.currentMongoIndexes?.map((mongoIndex: MongoIndex, index: number) => {
|
this.state.currentMongoIndexes?.map((mongoIndex: MongoIndex, index: number) => {
|
||||||
@@ -884,7 +912,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
const scaleComponentProps: ScaleComponentProps = {
|
const scaleComponentProps: ScaleComponentProps = {
|
||||||
collection: this.collection,
|
collection: this.collection,
|
||||||
database: this.database,
|
database: this.database,
|
||||||
container: this.container,
|
|
||||||
isFixedContainer: this.isFixedContainer,
|
isFixedContainer: this.isFixedContainer,
|
||||||
onThroughputChange: this.onThroughputChange,
|
onThroughputChange: this.onThroughputChange,
|
||||||
throughput: this.state.throughput,
|
throughput: this.state.throughput,
|
||||||
@@ -910,9 +937,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.state.offerLoaded) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
const subSettingsComponentProps: SubSettingsComponentProps = {
|
const subSettingsComponentProps: SubSettingsComponentProps = {
|
||||||
collection: this.collection,
|
collection: this.collection,
|
||||||
container: this.container,
|
|
||||||
isAnalyticalStorageEnabled: this.isAnalyticalStorageEnabled,
|
isAnalyticalStorageEnabled: this.isAnalyticalStorageEnabled,
|
||||||
changeFeedPolicyVisible: this.changeFeedPolicyVisible,
|
changeFeedPolicyVisible: this.changeFeedPolicyVisible,
|
||||||
timeToLive: this.state.timeToLive,
|
timeToLive: this.state.timeToLive,
|
||||||
@@ -965,7 +995,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
|
|
||||||
const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
|
const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
|
||||||
collection: this.collection,
|
collection: this.collection,
|
||||||
container: this.container,
|
|
||||||
conflictResolutionPolicyMode: this.state.conflictResolutionPolicyMode,
|
conflictResolutionPolicyMode: this.state.conflictResolutionPolicyMode,
|
||||||
conflictResolutionPolicyModeBaseline: this.state.conflictResolutionPolicyModeBaseline,
|
conflictResolutionPolicyModeBaseline: this.state.conflictResolutionPolicyModeBaseline,
|
||||||
onConflictResolutionPolicyModeChange: this.onConflictResolutionPolicyModeChange,
|
onConflictResolutionPolicyModeChange: this.onConflictResolutionPolicyModeChange,
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import ko from "knockout";
|
|
||||||
import * as React from "react";
|
|
||||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
|
||||||
import { SettingsComponent, SettingsComponentProps } from "./SettingsComponent";
|
|
||||||
|
|
||||||
export class SettingsComponentAdapter implements ReactAdapter {
|
|
||||||
public parameters: ko.Computed<boolean>;
|
|
||||||
|
|
||||||
constructor(private props: SettingsComponentProps) {}
|
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
|
||||||
return this.parameters() ? <SettingsComponent {...this.props} /> : <></>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-3
@@ -1,13 +1,12 @@
|
|||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { ConflictResolutionComponentProps, ConflictResolutionComponent } from "./ConflictResolutionComponent";
|
|
||||||
import { container, collection } from "../TestUtils";
|
|
||||||
import * as DataModels from "../../../../Contracts/DataModels";
|
import * as DataModels from "../../../../Contracts/DataModels";
|
||||||
|
import { collection } from "../TestUtils";
|
||||||
|
import { ConflictResolutionComponent, ConflictResolutionComponentProps } from "./ConflictResolutionComponent";
|
||||||
|
|
||||||
describe("ConflictResolutionComponent", () => {
|
describe("ConflictResolutionComponent", () => {
|
||||||
const baseProps: ConflictResolutionComponentProps = {
|
const baseProps: ConflictResolutionComponentProps = {
|
||||||
collection: collection,
|
collection: collection,
|
||||||
container: container,
|
|
||||||
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode.Custom,
|
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode.Custom,
|
||||||
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode.Custom,
|
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode.Custom,
|
||||||
onConflictResolutionPolicyModeChange: () => {
|
onConflictResolutionPolicyModeChange: () => {
|
||||||
|
|||||||
+6
-8
@@ -1,21 +1,19 @@
|
|||||||
|
import { ChoiceGroup, IChoiceGroupOption, ITextFieldProps, Stack, TextField } from "@fluentui/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ViewModels from "../../../../Contracts/ViewModels";
|
|
||||||
import * as DataModels from "../../../../Contracts/DataModels";
|
import * as DataModels from "../../../../Contracts/DataModels";
|
||||||
import Explorer from "../../../Explorer";
|
import * as ViewModels from "../../../../Contracts/ViewModels";
|
||||||
import {
|
import {
|
||||||
getTextFieldStyles,
|
|
||||||
conflictResolutionLwwTooltip,
|
|
||||||
conflictResolutionCustomToolTip,
|
conflictResolutionCustomToolTip,
|
||||||
subComponentStackProps,
|
conflictResolutionLwwTooltip,
|
||||||
getChoiceGroupStyles,
|
getChoiceGroupStyles,
|
||||||
|
getTextFieldStyles,
|
||||||
|
subComponentStackProps,
|
||||||
} from "../SettingsRenderUtils";
|
} from "../SettingsRenderUtils";
|
||||||
import { TextField, ITextFieldProps, Stack, IChoiceGroupOption, ChoiceGroup } from "@fluentui/react";
|
|
||||||
import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
|
|
||||||
import { isDirty } from "../SettingsUtils";
|
import { isDirty } from "../SettingsUtils";
|
||||||
|
import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
|
||||||
|
|
||||||
export interface ConflictResolutionComponentProps {
|
export interface ConflictResolutionComponentProps {
|
||||||
collection: ViewModels.Collection;
|
collection: ViewModels.Collection;
|
||||||
container: Explorer;
|
|
||||||
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
||||||
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
|
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
|
||||||
onConflictResolutionPolicyModeChange: (newMode: DataModels.ConflictResolutionMode) => void;
|
onConflictResolutionPolicyModeChange: (newMode: DataModels.ConflictResolutionMode) => void;
|
||||||
|
|||||||
@@ -7,20 +7,17 @@ import * as SharedConstants from "../../../../Shared/Constants";
|
|||||||
import { updateUserContext } from "../../../../UserContext";
|
import { updateUserContext } from "../../../../UserContext";
|
||||||
import Explorer from "../../../Explorer";
|
import Explorer from "../../../Explorer";
|
||||||
import { throughputUnit } from "../SettingsRenderUtils";
|
import { throughputUnit } from "../SettingsRenderUtils";
|
||||||
import { collection, container } from "../TestUtils";
|
import { collection } from "../TestUtils";
|
||||||
import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent";
|
import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent";
|
||||||
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
|
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
|
||||||
|
|
||||||
describe("ScaleComponent", () => {
|
describe("ScaleComponent", () => {
|
||||||
const nonNationalCloudContainer = new Explorer();
|
const nonNationalCloudContainer = new Explorer();
|
||||||
nonNationalCloudContainer.isRunningOnNationalCloud = () => false;
|
|
||||||
|
|
||||||
const targetThroughput = 6000;
|
const targetThroughput = 6000;
|
||||||
|
|
||||||
const baseProps: ScaleComponentProps = {
|
const baseProps: ScaleComponentProps = {
|
||||||
collection: collection,
|
collection: collection,
|
||||||
database: undefined,
|
database: undefined,
|
||||||
container: container,
|
|
||||||
isFixedContainer: false,
|
isFixedContainer: false,
|
||||||
onThroughputChange: () => {
|
onThroughputChange: () => {
|
||||||
return;
|
return;
|
||||||
@@ -111,7 +108,7 @@ describe("ScaleComponent", () => {
|
|||||||
let scaleComponent = new ScaleComponent(baseProps);
|
let scaleComponent = new ScaleComponent(baseProps);
|
||||||
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)");
|
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)");
|
||||||
|
|
||||||
let newProps = { ...baseProps, container: nonNationalCloudContainer };
|
let newProps = { ...baseProps };
|
||||||
scaleComponent = new ScaleComponent(newProps);
|
scaleComponent = new ScaleComponent(newProps);
|
||||||
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)");
|
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)");
|
||||||
|
|
||||||
@@ -124,7 +121,7 @@ describe("ScaleComponent", () => {
|
|||||||
let scaleComponent = new ScaleComponent(baseProps);
|
let scaleComponent = new ScaleComponent(baseProps);
|
||||||
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true);
|
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true);
|
||||||
|
|
||||||
const newProps = { ...baseProps, container: nonNationalCloudContainer };
|
const newProps = { ...baseProps };
|
||||||
scaleComponent = new ScaleComponent(newProps);
|
scaleComponent = new ScaleComponent(newProps);
|
||||||
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true);
|
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as ViewModels from "../../../../Contracts/ViewModels";
|
|||||||
import * as SharedConstants from "../../../../Shared/Constants";
|
import * as SharedConstants from "../../../../Shared/Constants";
|
||||||
import { userContext } from "../../../../UserContext";
|
import { userContext } from "../../../../UserContext";
|
||||||
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
|
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
|
||||||
import Explorer from "../../../Explorer";
|
import { isRunningOnNationalCloud } from "../../../../Utils/CloudUtils";
|
||||||
import {
|
import {
|
||||||
getTextFieldStyles,
|
getTextFieldStyles,
|
||||||
getThroughputApplyLongDelayMessage,
|
getThroughputApplyLongDelayMessage,
|
||||||
@@ -23,7 +23,6 @@ import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents
|
|||||||
export interface ScaleComponentProps {
|
export interface ScaleComponentProps {
|
||||||
collection: ViewModels.Collection;
|
collection: ViewModels.Collection;
|
||||||
database: ViewModels.Database;
|
database: ViewModels.Database;
|
||||||
container: Explorer;
|
|
||||||
isFixedContainer: boolean;
|
isFixedContainer: boolean;
|
||||||
onThroughputChange: (newThroughput: number) => void;
|
onThroughputChange: (newThroughput: number) => void;
|
||||||
throughput: number;
|
throughput: number;
|
||||||
@@ -109,11 +108,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public canThroughputExceedMaximumValue = (): boolean => {
|
public canThroughputExceedMaximumValue = (): boolean => {
|
||||||
return (
|
return !this.props.isFixedContainer && configContext.platform === Platform.Portal && !isRunningOnNationalCloud();
|
||||||
!this.props.isFixedContainer &&
|
|
||||||
configContext.platform === Platform.Portal &&
|
|
||||||
!this.props.container.isRunningOnNationalCloud()
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public getInitialNotificationElement = (): JSX.Element => {
|
public getInitialNotificationElement = (): JSX.Element => {
|
||||||
|
|||||||
@@ -4,14 +4,12 @@ import { DatabaseAccount } from "../../../../Contracts/DataModels";
|
|||||||
import { updateUserContext } from "../../../../UserContext";
|
import { updateUserContext } from "../../../../UserContext";
|
||||||
import Explorer from "../../../Explorer";
|
import Explorer from "../../../Explorer";
|
||||||
import { ChangeFeedPolicyState, GeospatialConfigType, TtlOff, TtlOn, TtlOnNoDefault, TtlType } from "../SettingsUtils";
|
import { ChangeFeedPolicyState, GeospatialConfigType, TtlOff, TtlOn, TtlOnNoDefault, TtlType } from "../SettingsUtils";
|
||||||
import { collection, container } from "../TestUtils";
|
import { collection } from "../TestUtils";
|
||||||
import { SubSettingsComponent, SubSettingsComponentProps } from "./SubSettingsComponent";
|
import { SubSettingsComponent, SubSettingsComponentProps } from "./SubSettingsComponent";
|
||||||
|
|
||||||
describe("SubSettingsComponent", () => {
|
describe("SubSettingsComponent", () => {
|
||||||
const baseProps: SubSettingsComponentProps = {
|
const baseProps: SubSettingsComponentProps = {
|
||||||
collection: collection,
|
collection: collection,
|
||||||
container: container,
|
|
||||||
|
|
||||||
timeToLive: TtlType.On,
|
timeToLive: TtlType.On,
|
||||||
timeToLiveBaseline: TtlType.On,
|
timeToLiveBaseline: TtlType.On,
|
||||||
onTtlChange: () => {
|
onTtlChange: () => {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { ChoiceGroup, IChoiceGroupOption, Label, Link, MessageBar, Stack, Text,
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ViewModels from "../../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../../Contracts/ViewModels";
|
||||||
import { userContext } from "../../../../UserContext";
|
import { userContext } from "../../../../UserContext";
|
||||||
import Explorer from "../../../Explorer";
|
|
||||||
import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
||||||
import {
|
import {
|
||||||
changeFeedPolicyToolTip,
|
changeFeedPolicyToolTip,
|
||||||
@@ -28,8 +27,6 @@ import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
|
|||||||
|
|
||||||
export interface SubSettingsComponentProps {
|
export interface SubSettingsComponentProps {
|
||||||
collection: ViewModels.Collection;
|
collection: ViewModels.Collection;
|
||||||
container: Explorer;
|
|
||||||
|
|
||||||
timeToLive: TtlType;
|
timeToLive: TtlType;
|
||||||
timeToLiveBaseline: TtlType;
|
timeToLiveBaseline: TtlType;
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ describe("SettingsUtils", () => {
|
|||||||
isDatabaseExpanded: ko.observable(false),
|
isDatabaseExpanded: ko.observable(false),
|
||||||
isDatabaseShared: ko.computed(() => true),
|
isDatabaseShared: ko.computed(() => true),
|
||||||
selectedSubnodeKind: ko.observable(undefined),
|
selectedSubnodeKind: ko.observable(undefined),
|
||||||
selectDatabase: undefined,
|
|
||||||
expandDatabase: undefined,
|
expandDatabase: undefined,
|
||||||
collapseDatabase: undefined,
|
collapseDatabase: undefined,
|
||||||
loadCollections: undefined,
|
loadCollections: undefined,
|
||||||
|
|||||||
@@ -30,17 +30,11 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"container": Explorer {
|
"container": Explorer {
|
||||||
"_isInitializingNotebooks": false,
|
"_isInitializingNotebooks": false,
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"canSaveQueries": [Function],
|
|
||||||
"collapsedResourceTreeWidth": 36,
|
|
||||||
"databases": [Function],
|
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
"isHostedDataExplorerEnabled": [Function],
|
|
||||||
"isNotebookEnabled": [Function],
|
"isNotebookEnabled": [Function],
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
"isNotebooksEnabledForAccount": [Function],
|
||||||
"isResourceTokenCollectionNodeSelected": [Function],
|
|
||||||
"isSchemaEnabled": [Function],
|
"isSchemaEnabled": [Function],
|
||||||
"isServerlessEnabled": [Function],
|
|
||||||
"isShellEnabled": [Function],
|
"isShellEnabled": [Function],
|
||||||
"isSynapseLinkUpdating": [Function],
|
"isSynapseLinkUpdating": [Function],
|
||||||
"isTabsContentExpanded": [Function],
|
"isTabsContentExpanded": [Function],
|
||||||
@@ -55,38 +49,16 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
},
|
},
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"resourceTokenCollection": [Function],
|
"resourceTokenCollection": [Function],
|
||||||
"resourceTokenCollectionId": [Function],
|
|
||||||
"resourceTokenDatabaseId": [Function],
|
|
||||||
"resourceTokenPartitionKey": [Function],
|
|
||||||
"resourceTree": ResourceTreeAdapter {
|
"resourceTree": ResourceTreeAdapter {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"copyNotebook": [Function],
|
"copyNotebook": [Function],
|
||||||
"databaseCollectionIdMap": Map {},
|
|
||||||
"koSubsCollectionIdMap": Map {},
|
|
||||||
"koSubsDatabaseIdMap": Map {},
|
|
||||||
"parameters": [Function],
|
"parameters": [Function],
|
||||||
},
|
},
|
||||||
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"parameters": [Function],
|
"parameters": [Function],
|
||||||
},
|
},
|
||||||
"selectedDatabaseId": [Function],
|
|
||||||
"selectedNode": [Function],
|
|
||||||
"setInProgressConsoleDataIdToBeDeleted": undefined,
|
|
||||||
"setNotificationConsoleData": undefined,
|
|
||||||
"sparkClusterConnectionInfo": [Function],
|
"sparkClusterConnectionInfo": [Function],
|
||||||
"splitter": Splitter {
|
|
||||||
"bounds": Object {
|
|
||||||
"max": 400,
|
|
||||||
"min": 240,
|
|
||||||
},
|
|
||||||
"direction": "vertical",
|
|
||||||
"isCollapsed": [Function],
|
|
||||||
"leftSideId": "resourcetree",
|
|
||||||
"onResizeStart": [Function],
|
|
||||||
"onResizeStop": [Function],
|
|
||||||
"splitterId": "h_splitter1",
|
|
||||||
},
|
|
||||||
"tabsManager": TabsManager {
|
"tabsManager": TabsManager {
|
||||||
"activeTab": [Function],
|
"activeTab": [Function],
|
||||||
"openedTabs": [Function],
|
"openedTabs": [Function],
|
||||||
@@ -110,73 +82,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"usageSizeInKB": [Function],
|
"usageSizeInKB": [Function],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
container={
|
|
||||||
Explorer {
|
|
||||||
"_isInitializingNotebooks": false,
|
|
||||||
"_resetNotebookWorkspace": [Function],
|
|
||||||
"canSaveQueries": [Function],
|
|
||||||
"collapsedResourceTreeWidth": 36,
|
|
||||||
"databases": [Function],
|
|
||||||
"isAccountReady": [Function],
|
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
|
||||||
"isHostedDataExplorerEnabled": [Function],
|
|
||||||
"isNotebookEnabled": [Function],
|
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
|
||||||
"isResourceTokenCollectionNodeSelected": [Function],
|
|
||||||
"isSchemaEnabled": [Function],
|
|
||||||
"isServerlessEnabled": [Function],
|
|
||||||
"isShellEnabled": [Function],
|
|
||||||
"isSynapseLinkUpdating": [Function],
|
|
||||||
"isTabsContentExpanded": [Function],
|
|
||||||
"memoryUsageInfo": [Function],
|
|
||||||
"notebookBasePath": [Function],
|
|
||||||
"notebookServerInfo": [Function],
|
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
|
||||||
"onRefreshResourcesClick": [Function],
|
|
||||||
"provideFeedbackEmail": [Function],
|
|
||||||
"queriesClient": QueriesClient {
|
|
||||||
"container": [Circular],
|
|
||||||
},
|
|
||||||
"refreshNotebookList": [Function],
|
|
||||||
"resourceTokenCollection": [Function],
|
|
||||||
"resourceTokenCollectionId": [Function],
|
|
||||||
"resourceTokenDatabaseId": [Function],
|
|
||||||
"resourceTokenPartitionKey": [Function],
|
|
||||||
"resourceTree": ResourceTreeAdapter {
|
|
||||||
"container": [Circular],
|
|
||||||
"copyNotebook": [Function],
|
|
||||||
"databaseCollectionIdMap": Map {},
|
|
||||||
"koSubsCollectionIdMap": Map {},
|
|
||||||
"koSubsDatabaseIdMap": Map {},
|
|
||||||
"parameters": [Function],
|
|
||||||
},
|
|
||||||
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
|
||||||
"container": [Circular],
|
|
||||||
"parameters": [Function],
|
|
||||||
},
|
|
||||||
"selectedDatabaseId": [Function],
|
|
||||||
"selectedNode": [Function],
|
|
||||||
"setInProgressConsoleDataIdToBeDeleted": undefined,
|
|
||||||
"setNotificationConsoleData": undefined,
|
|
||||||
"sparkClusterConnectionInfo": [Function],
|
|
||||||
"splitter": Splitter {
|
|
||||||
"bounds": Object {
|
|
||||||
"max": 400,
|
|
||||||
"min": 240,
|
|
||||||
},
|
|
||||||
"direction": "vertical",
|
|
||||||
"isCollapsed": [Function],
|
|
||||||
"leftSideId": "resourcetree",
|
|
||||||
"onResizeStart": [Function],
|
|
||||||
"onResizeStop": [Function],
|
|
||||||
"splitterId": "h_splitter1",
|
|
||||||
},
|
|
||||||
"tabsManager": TabsManager {
|
|
||||||
"activeTab": [Function],
|
|
||||||
"openedTabs": [Function],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isAutoPilotSelected={false}
|
isAutoPilotSelected={false}
|
||||||
isFixedContainer={false}
|
isFixedContainer={false}
|
||||||
onAutoPilotSelected={[Function]}
|
onAutoPilotSelected={[Function]}
|
||||||
@@ -211,17 +116,11 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"container": Explorer {
|
"container": Explorer {
|
||||||
"_isInitializingNotebooks": false,
|
"_isInitializingNotebooks": false,
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"canSaveQueries": [Function],
|
|
||||||
"collapsedResourceTreeWidth": 36,
|
|
||||||
"databases": [Function],
|
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
"isHostedDataExplorerEnabled": [Function],
|
|
||||||
"isNotebookEnabled": [Function],
|
"isNotebookEnabled": [Function],
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
"isNotebooksEnabledForAccount": [Function],
|
||||||
"isResourceTokenCollectionNodeSelected": [Function],
|
|
||||||
"isSchemaEnabled": [Function],
|
"isSchemaEnabled": [Function],
|
||||||
"isServerlessEnabled": [Function],
|
|
||||||
"isShellEnabled": [Function],
|
"isShellEnabled": [Function],
|
||||||
"isSynapseLinkUpdating": [Function],
|
"isSynapseLinkUpdating": [Function],
|
||||||
"isTabsContentExpanded": [Function],
|
"isTabsContentExpanded": [Function],
|
||||||
@@ -236,38 +135,16 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
},
|
},
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"resourceTokenCollection": [Function],
|
"resourceTokenCollection": [Function],
|
||||||
"resourceTokenCollectionId": [Function],
|
|
||||||
"resourceTokenDatabaseId": [Function],
|
|
||||||
"resourceTokenPartitionKey": [Function],
|
|
||||||
"resourceTree": ResourceTreeAdapter {
|
"resourceTree": ResourceTreeAdapter {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"copyNotebook": [Function],
|
"copyNotebook": [Function],
|
||||||
"databaseCollectionIdMap": Map {},
|
|
||||||
"koSubsCollectionIdMap": Map {},
|
|
||||||
"koSubsDatabaseIdMap": Map {},
|
|
||||||
"parameters": [Function],
|
"parameters": [Function],
|
||||||
},
|
},
|
||||||
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"parameters": [Function],
|
"parameters": [Function],
|
||||||
},
|
},
|
||||||
"selectedDatabaseId": [Function],
|
|
||||||
"selectedNode": [Function],
|
|
||||||
"setInProgressConsoleDataIdToBeDeleted": undefined,
|
|
||||||
"setNotificationConsoleData": undefined,
|
|
||||||
"sparkClusterConnectionInfo": [Function],
|
"sparkClusterConnectionInfo": [Function],
|
||||||
"splitter": Splitter {
|
|
||||||
"bounds": Object {
|
|
||||||
"max": 400,
|
|
||||||
"min": 240,
|
|
||||||
},
|
|
||||||
"direction": "vertical",
|
|
||||||
"isCollapsed": [Function],
|
|
||||||
"leftSideId": "resourcetree",
|
|
||||||
"onResizeStart": [Function],
|
|
||||||
"onResizeStop": [Function],
|
|
||||||
"splitterId": "h_splitter1",
|
|
||||||
},
|
|
||||||
"tabsManager": TabsManager {
|
"tabsManager": TabsManager {
|
||||||
"activeTab": [Function],
|
"activeTab": [Function],
|
||||||
"openedTabs": [Function],
|
"openedTabs": [Function],
|
||||||
@@ -291,73 +168,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"usageSizeInKB": [Function],
|
"usageSizeInKB": [Function],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
container={
|
|
||||||
Explorer {
|
|
||||||
"_isInitializingNotebooks": false,
|
|
||||||
"_resetNotebookWorkspace": [Function],
|
|
||||||
"canSaveQueries": [Function],
|
|
||||||
"collapsedResourceTreeWidth": 36,
|
|
||||||
"databases": [Function],
|
|
||||||
"isAccountReady": [Function],
|
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
|
||||||
"isHostedDataExplorerEnabled": [Function],
|
|
||||||
"isNotebookEnabled": [Function],
|
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
|
||||||
"isResourceTokenCollectionNodeSelected": [Function],
|
|
||||||
"isSchemaEnabled": [Function],
|
|
||||||
"isServerlessEnabled": [Function],
|
|
||||||
"isShellEnabled": [Function],
|
|
||||||
"isSynapseLinkUpdating": [Function],
|
|
||||||
"isTabsContentExpanded": [Function],
|
|
||||||
"memoryUsageInfo": [Function],
|
|
||||||
"notebookBasePath": [Function],
|
|
||||||
"notebookServerInfo": [Function],
|
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
|
||||||
"onRefreshResourcesClick": [Function],
|
|
||||||
"provideFeedbackEmail": [Function],
|
|
||||||
"queriesClient": QueriesClient {
|
|
||||||
"container": [Circular],
|
|
||||||
},
|
|
||||||
"refreshNotebookList": [Function],
|
|
||||||
"resourceTokenCollection": [Function],
|
|
||||||
"resourceTokenCollectionId": [Function],
|
|
||||||
"resourceTokenDatabaseId": [Function],
|
|
||||||
"resourceTokenPartitionKey": [Function],
|
|
||||||
"resourceTree": ResourceTreeAdapter {
|
|
||||||
"container": [Circular],
|
|
||||||
"copyNotebook": [Function],
|
|
||||||
"databaseCollectionIdMap": Map {},
|
|
||||||
"koSubsCollectionIdMap": Map {},
|
|
||||||
"koSubsDatabaseIdMap": Map {},
|
|
||||||
"parameters": [Function],
|
|
||||||
},
|
|
||||||
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
|
||||||
"container": [Circular],
|
|
||||||
"parameters": [Function],
|
|
||||||
},
|
|
||||||
"selectedDatabaseId": [Function],
|
|
||||||
"selectedNode": [Function],
|
|
||||||
"setInProgressConsoleDataIdToBeDeleted": undefined,
|
|
||||||
"setNotificationConsoleData": undefined,
|
|
||||||
"sparkClusterConnectionInfo": [Function],
|
|
||||||
"splitter": Splitter {
|
|
||||||
"bounds": Object {
|
|
||||||
"max": 400,
|
|
||||||
"min": 240,
|
|
||||||
},
|
|
||||||
"direction": "vertical",
|
|
||||||
"isCollapsed": [Function],
|
|
||||||
"leftSideId": "resourcetree",
|
|
||||||
"onResizeStart": [Function],
|
|
||||||
"onResizeStop": [Function],
|
|
||||||
"splitterId": "h_splitter1",
|
|
||||||
},
|
|
||||||
"tabsManager": TabsManager {
|
|
||||||
"activeTab": [Function],
|
|
||||||
"openedTabs": [Function],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
geospatialConfigType="Geometry"
|
geospatialConfigType="Geometry"
|
||||||
geospatialConfigTypeBaseline="Geometry"
|
geospatialConfigTypeBaseline="Geometry"
|
||||||
isAnalyticalStorageEnabled={false}
|
isAnalyticalStorageEnabled={false}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export interface TreeComponentProps {
|
|||||||
export class TreeComponent extends React.Component<TreeComponentProps> {
|
export class TreeComponent extends React.Component<TreeComponentProps> {
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div style={this.props.style} className={`treeComponent ${this.props.className}`}>
|
<div style={this.props.style} className={`treeComponent ${this.props.className}`} role="tree">
|
||||||
<TreeNodeComponent paddingLeft={0} node={this.props.rootNode} generation={0} />
|
<TreeNodeComponent paddingLeft={0} node={this.props.rootNode} generation={0} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -172,6 +172,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
|||||||
className={`${this.props.node.className || ""} main${generation} nodeItem ${showSelected ? "selected" : ""}`}
|
className={`${this.props.node.className || ""} main${generation} nodeItem ${showSelected ? "selected" : ""}`}
|
||||||
onClick={(event: React.MouseEvent<HTMLDivElement>) => this.onNodeClick(event, node)}
|
onClick={(event: React.MouseEvent<HTMLDivElement>) => this.onNodeClick(event, node)}
|
||||||
onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) => this.onNodeKeyPress(event, node)}
|
onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) => this.onNodeKeyPress(event, node)}
|
||||||
|
role="treeitem"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`treeNodeHeader ${this.state.isMenuShowing ? "showingMenu" : ""}`}
|
className={`treeNodeHeader ${this.state.isMenuShowing ? "showingMenu" : ""}`}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
exports[`TreeComponent renders a simple tree 1`] = `
|
exports[`TreeComponent renders a simple tree 1`] = `
|
||||||
<div
|
<div
|
||||||
className="treeComponent tree"
|
className="treeComponent tree"
|
||||||
|
role="tree"
|
||||||
>
|
>
|
||||||
<TreeNodeComponent
|
<TreeNodeComponent
|
||||||
generation={0}
|
generation={0}
|
||||||
@@ -37,6 +38,7 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
|
|||||||
className=" main2 nodeItem "
|
className=" main2 nodeItem "
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyPress={[Function]}
|
onKeyPress={[Function]}
|
||||||
|
role="treeitem"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="treeNodeHeader "
|
className="treeNodeHeader "
|
||||||
@@ -137,6 +139,7 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
|
|||||||
className="nodeClassname main12 nodeItem "
|
className="nodeClassname main12 nodeItem "
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyPress={[Function]}
|
onKeyPress={[Function]}
|
||||||
|
role="treeitem"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="treeNodeHeader "
|
className="treeNodeHeader "
|
||||||
@@ -285,6 +288,7 @@ exports[`TreeNodeComponent renders loading icon 1`] = `
|
|||||||
className=" main2 nodeItem "
|
className=" main2 nodeItem "
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyPress={[Function]}
|
onKeyPress={[Function]}
|
||||||
|
role="treeitem"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="treeNodeHeader "
|
className="treeNodeHeader "
|
||||||
@@ -356,6 +360,7 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
|
|||||||
className="nodeClassname main12 nodeItem "
|
className="nodeClassname main12 nodeItem "
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyPress={[Function]}
|
onKeyPress={[Function]}
|
||||||
|
role="treeitem"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="treeNodeHeader "
|
className="treeNodeHeader "
|
||||||
@@ -523,6 +528,7 @@ exports[`TreeNodeComponent renders unsorted children by default 1`] = `
|
|||||||
className=" main2 nodeItem "
|
className=" main2 nodeItem "
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyPress={[Function]}
|
onKeyPress={[Function]}
|
||||||
|
role="treeitem"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="treeNodeHeader "
|
className="treeNodeHeader "
|
||||||
|
|||||||
@@ -2,22 +2,22 @@ jest.mock("../Graph/GraphExplorerComponent/GremlinClient");
|
|||||||
jest.mock("../../Common/dataAccess/createCollection");
|
jest.mock("../../Common/dataAccess/createCollection");
|
||||||
jest.mock("../../Common/dataAccess/createDocument");
|
jest.mock("../../Common/dataAccess/createDocument");
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import Q from "q";
|
|
||||||
import { createDocument } from "../../Common/dataAccess/createDocument";
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
import { DatabaseAccount } from "../../Contracts/DataModels";
|
import { DatabaseAccount } from "../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
|
import { useDatabases } from "../useDatabases";
|
||||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||||
|
|
||||||
describe("ContainerSampleGenerator", () => {
|
describe("ContainerSampleGenerator", () => {
|
||||||
const createExplorerStub = (database: ViewModels.Database): Explorer => {
|
let explorerStub: Explorer;
|
||||||
const explorerStub = {} as Explorer;
|
|
||||||
explorerStub.databases = ko.observableArray<ViewModels.Database>([database]);
|
beforeAll(() => {
|
||||||
explorerStub.findDatabaseWithId = () => database;
|
explorerStub = {
|
||||||
explorerStub.refreshAllDatabases = () => Q.resolve();
|
refreshAllDatabases: () => {},
|
||||||
return explorerStub;
|
} as Explorer;
|
||||||
};
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(createDocument as jest.Mock).mockResolvedValue(undefined);
|
(createDocument as jest.Mock).mockResolvedValue(undefined);
|
||||||
@@ -59,8 +59,7 @@ describe("ContainerSampleGenerator", () => {
|
|||||||
loadCollections: () => {},
|
loadCollections: () => {},
|
||||||
} as ViewModels.Database;
|
} as ViewModels.Database;
|
||||||
database.findCollectionWithId = () => collection;
|
database.findCollectionWithId = () => collection;
|
||||||
|
useDatabases.getState().addDatabases([database]);
|
||||||
const explorerStub = createExplorerStub(database);
|
|
||||||
|
|
||||||
const generator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub);
|
const generator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub);
|
||||||
generator.setData(sampleData);
|
generator.setData(sampleData);
|
||||||
@@ -108,8 +107,8 @@ describe("ContainerSampleGenerator", () => {
|
|||||||
} as ViewModels.Database;
|
} as ViewModels.Database;
|
||||||
database.findCollectionWithId = () => collection;
|
database.findCollectionWithId = () => collection;
|
||||||
collection.databaseId = database.id();
|
collection.databaseId = database.id();
|
||||||
|
useDatabases.getState().addDatabases([database]);
|
||||||
|
|
||||||
const explorerStub = createExplorerStub(database);
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
properties: {
|
properties: {
|
||||||
@@ -126,7 +125,6 @@ describe("ContainerSampleGenerator", () => {
|
|||||||
|
|
||||||
it("should not create any sample for Mongo API account", async () => {
|
it("should not create any sample for Mongo API account", async () => {
|
||||||
const experience = "Sample generation not supported for this API Mongo";
|
const experience = "Sample generation not supported for this API Mongo";
|
||||||
const explorerStub = createExplorerStub(undefined);
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
properties: {
|
properties: {
|
||||||
@@ -141,7 +139,6 @@ describe("ContainerSampleGenerator", () => {
|
|||||||
|
|
||||||
it("should not create any sample for Table API account", async () => {
|
it("should not create any sample for Table API account", async () => {
|
||||||
const experience = "Sample generation not supported for this API Tables";
|
const experience = "Sample generation not supported for this API Tables";
|
||||||
const explorerStub = createExplorerStub(undefined);
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
properties: {
|
properties: {
|
||||||
@@ -163,7 +160,6 @@ describe("ContainerSampleGenerator", () => {
|
|||||||
},
|
},
|
||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
});
|
});
|
||||||
const explorerStub = createExplorerStub(undefined);
|
|
||||||
// Rejects with error that contains experience
|
// Rejects with error that contains experience
|
||||||
await expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
|
await expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"
|
|||||||
import GraphTab from ".././Tabs/GraphTab";
|
import GraphTab from ".././Tabs/GraphTab";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
|
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
|
||||||
|
import { useDatabases } from "../useDatabases";
|
||||||
|
|
||||||
interface SampleDataFile extends DataModels.CreateCollectionParams {
|
interface SampleDataFile extends DataModels.CreateCollectionParams {
|
||||||
data: any[];
|
data: any[];
|
||||||
@@ -59,7 +60,7 @@ export class ContainerSampleGenerator {
|
|||||||
|
|
||||||
await createCollection(createRequest);
|
await createCollection(createRequest);
|
||||||
await this.container.refreshAllDatabases();
|
await this.container.refreshAllDatabases();
|
||||||
const database = this.container.findDatabaseWithId(this.sampleDataFile.databaseId);
|
const database = useDatabases.getState().findDatabaseWithId(this.sampleDataFile.databaseId);
|
||||||
if (!database) {
|
if (!database) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as ko from "knockout";
|
|||||||
import * as sinon from "sinon";
|
import * as sinon from "sinon";
|
||||||
import { Collection, Database } from "../../Contracts/ViewModels";
|
import { Collection, Database } from "../../Contracts/ViewModels";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
|
import { useDatabases } from "../useDatabases";
|
||||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||||
import { DataSamplesUtil } from "./DataSamplesUtil";
|
import { DataSamplesUtil } from "./DataSamplesUtil";
|
||||||
|
|
||||||
@@ -16,8 +17,8 @@ describe("DataSampleUtils", () => {
|
|||||||
collections: ko.observableArray<Collection>([collection]),
|
collections: ko.observableArray<Collection>([collection]),
|
||||||
} as Database;
|
} as Database;
|
||||||
const explorer = {} as Explorer;
|
const explorer = {} as Explorer;
|
||||||
explorer.databases = ko.observableArray<Database>([database]);
|
|
||||||
explorer.showOkModalDialog = () => {};
|
explorer.showOkModalDialog = () => {};
|
||||||
|
useDatabases.getState().addDatabases([database]);
|
||||||
const dataSamplesUtil = new DataSamplesUtil(explorer);
|
const dataSamplesUtil = new DataSamplesUtil(explorer);
|
||||||
|
|
||||||
const fakeGenerator = sinon.createStubInstance<ContainerSampleGenerator>(ContainerSampleGenerator as any);
|
const fakeGenerator = sinon.createStubInstance<ContainerSampleGenerator>(ContainerSampleGenerator as any);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
|||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
|
import { useDatabases } from "../useDatabases";
|
||||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||||
|
|
||||||
export class DataSamplesUtil {
|
export class DataSamplesUtil {
|
||||||
@@ -17,7 +18,7 @@ export class DataSamplesUtil {
|
|||||||
|
|
||||||
const databaseName = generator.getDatabaseId();
|
const databaseName = generator.getDatabaseId();
|
||||||
const containerName = generator.getCollectionId();
|
const containerName = generator.getCollectionId();
|
||||||
if (this.hasContainer(databaseName, containerName, this.container.databases())) {
|
if (this.hasContainer(databaseName, containerName, useDatabases.getState().databases)) {
|
||||||
const msg = `The container ${containerName} in database ${databaseName} already exists. Please delete it and retry.`;
|
const msg = `The container ${containerName} in database ${databaseName} already exists. Please delete it and retry.`;
|
||||||
this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
|
this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
|
||||||
logConsoleError(msg);
|
logConsoleError(msg);
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
jest.mock("./../Common/dataAccess/deleteDatabase");
|
|
||||||
jest.mock("./../Shared/Telemetry/TelemetryProcessor");
|
|
||||||
import * as ko from "knockout";
|
|
||||||
import { deleteDatabase } from "./../Common/dataAccess/deleteDatabase";
|
|
||||||
import * as ViewModels from "./../Contracts/ViewModels";
|
|
||||||
import Explorer from "./Explorer";
|
|
||||||
|
|
||||||
describe("Explorer.isLastDatabase() and Explorer.isLastNonEmptyDatabase()", () => {
|
|
||||||
let explorer: Explorer;
|
|
||||||
beforeAll(() => {
|
|
||||||
(deleteDatabase as jest.Mock).mockResolvedValue(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
explorer = new Explorer();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be true if only 1 database", () => {
|
|
||||||
const database = {} as ViewModels.Database;
|
|
||||||
explorer.databases = ko.observableArray<ViewModels.Database>([database]);
|
|
||||||
expect(explorer.isLastDatabase()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be false if only 2 databases", () => {
|
|
||||||
const database = {} as ViewModels.Database;
|
|
||||||
const database2 = {} as ViewModels.Database;
|
|
||||||
explorer.databases = ko.observableArray<ViewModels.Database>([database, database2]);
|
|
||||||
expect(explorer.isLastDatabase()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be false if not last empty database", () => {
|
|
||||||
const database = {} as ViewModels.Database;
|
|
||||||
explorer.databases = ko.observableArray<ViewModels.Database>([database]);
|
|
||||||
expect(explorer.isLastNonEmptyDatabase()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be true if last non empty database", () => {
|
|
||||||
const database = {} as ViewModels.Database;
|
|
||||||
database.collections = ko.observableArray<ViewModels.Collection>([{} as ViewModels.Collection]);
|
|
||||||
explorer.databases = ko.observableArray<ViewModels.Database>([database]);
|
|
||||||
expect(explorer.isLastNonEmptyDatabase()).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
+69
-403
@@ -6,28 +6,30 @@ import _ from "underscore";
|
|||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
||||||
import * as Constants from "../Common/Constants";
|
import * as Constants from "../Common/Constants";
|
||||||
import { ExplorerMetrics } from "../Common/Constants";
|
|
||||||
import { readCollection } from "../Common/dataAccess/readCollection";
|
import { readCollection } from "../Common/dataAccess/readCollection";
|
||||||
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
||||||
import { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility";
|
import { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility";
|
||||||
import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils";
|
||||||
import * as Logger from "../Common/Logger";
|
import * as Logger from "../Common/Logger";
|
||||||
import { QueriesClient } from "../Common/QueriesClient";
|
import { QueriesClient } from "../Common/QueriesClient";
|
||||||
import { Splitter, SplitterBounds, SplitterDirection } from "../Common/Splitter";
|
import { configContext } from "../ConfigContext";
|
||||||
import { configContext, Platform } from "../ConfigContext";
|
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
||||||
import { useSidePanel } from "../hooks/useSidePanel";
|
import { useSidePanel } from "../hooks/useSidePanel";
|
||||||
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
|
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
|
||||||
import { NotebookWorkspaceManager } from "../NotebookWorkspaceManager/NotebookWorkspaceManager";
|
|
||||||
import { RouteHandler } from "../RouteHandlers/RouteHandler";
|
|
||||||
import { ExplorerSettings } from "../Shared/ExplorerSettings";
|
import { ExplorerSettings } from "../Shared/ExplorerSettings";
|
||||||
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { getCollectionName, getDatabaseName, getUploadName } from "../Utils/APITypeUtils";
|
import { getCollectionName, getDatabaseName, getUploadName } from "../Utils/APITypeUtils";
|
||||||
import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
|
import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
|
||||||
|
import {
|
||||||
|
get as getWorkspace,
|
||||||
|
listByDatabaseAccount,
|
||||||
|
listConnectionInfo,
|
||||||
|
start,
|
||||||
|
} from "../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
||||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||||
import { stringToBlob } from "../Utils/BlobUtils";
|
import { stringToBlob } from "../Utils/BlobUtils";
|
||||||
import { isCapabilityEnabled } from "../Utils/CapabilityUtils";
|
import { isCapabilityEnabled } from "../Utils/CapabilityUtils";
|
||||||
@@ -38,7 +40,6 @@ import * as ComponentRegisterer from "./ComponentRegisterer";
|
|||||||
import { DialogProps, TextFieldProps, useDialog } from "./Controls/Dialog";
|
import { DialogProps, TextFieldProps, useDialog } from "./Controls/Dialog";
|
||||||
import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent";
|
import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent";
|
||||||
import { useCommandBar } from "./Menus/CommandBar/CommandBarComponentAdapter";
|
import { useCommandBar } from "./Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { ConsoleData } from "./Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import * as FileSystemUtil from "./Notebook/FileSystemUtil";
|
import * as FileSystemUtil from "./Notebook/FileSystemUtil";
|
||||||
import { SnapshotRequest } from "./Notebook/NotebookComponent/types";
|
import { SnapshotRequest } from "./Notebook/NotebookComponent/types";
|
||||||
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
|
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
|
||||||
@@ -49,24 +50,15 @@ import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
|
|||||||
import { AddDatabasePanel } from "./Panes/AddDatabasePanel/AddDatabasePanel";
|
import { AddDatabasePanel } from "./Panes/AddDatabasePanel/AddDatabasePanel";
|
||||||
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane/BrowseQueriesPane";
|
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane/BrowseQueriesPane";
|
||||||
import { CassandraAddCollectionPane } from "./Panes/CassandraAddCollectionPane/CassandraAddCollectionPane";
|
import { CassandraAddCollectionPane } from "./Panes/CassandraAddCollectionPane/CassandraAddCollectionPane";
|
||||||
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
|
|
||||||
import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel";
|
|
||||||
import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane";
|
import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane";
|
||||||
import { GitHubReposPanel } from "./Panes/GitHubReposPanel/GitHubReposPanel";
|
import { GitHubReposPanel } from "./Panes/GitHubReposPanel/GitHubReposPanel";
|
||||||
import { SaveQueryPane } from "./Panes/SaveQueryPane/SaveQueryPane";
|
import { SaveQueryPane } from "./Panes/SaveQueryPane/SaveQueryPane";
|
||||||
import { SettingsPane } from "./Panes/SettingsPane/SettingsPane";
|
|
||||||
import { SetupNoteBooksPanel } from "./Panes/SetupNotebooksPanel/SetupNotebooksPanel";
|
import { SetupNoteBooksPanel } from "./Panes/SetupNotebooksPanel/SetupNotebooksPanel";
|
||||||
import { StringInputPane } from "./Panes/StringInputPane/StringInputPane";
|
import { StringInputPane } from "./Panes/StringInputPane/StringInputPane";
|
||||||
import { AddTableEntityPanel } from "./Panes/Tables/AddTableEntityPanel";
|
|
||||||
import { EditTableEntityPanel } from "./Panes/Tables/EditTableEntityPanel";
|
|
||||||
import { TableQuerySelectPanel } from "./Panes/Tables/TableQuerySelectPanel/TableQuerySelectPanel";
|
|
||||||
import { UploadFilePane } from "./Panes/UploadFilePane/UploadFilePane";
|
import { UploadFilePane } from "./Panes/UploadFilePane/UploadFilePane";
|
||||||
import { UploadItemsPane } from "./Panes/UploadItemsPane/UploadItemsPane";
|
import { UploadItemsPane } from "./Panes/UploadItemsPane/UploadItemsPane";
|
||||||
import TableListViewModal from "./Tables/DataTable/TableEntityListViewModel";
|
|
||||||
import QueryViewModel from "./Tables/QueryBuilder/QueryViewModel";
|
|
||||||
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
|
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
|
||||||
import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab";
|
import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab";
|
||||||
import QueryTablesTab from "./Tabs/QueryTablesTab";
|
|
||||||
import { TabsManager } from "./Tabs/TabsManager";
|
import { TabsManager } from "./Tabs/TabsManager";
|
||||||
import TerminalTab from "./Tabs/TerminalTab";
|
import TerminalTab from "./Tabs/TerminalTab";
|
||||||
import Database from "./Tree/Database";
|
import Database from "./Tree/Database";
|
||||||
@@ -74,44 +66,28 @@ import ResourceTokenCollection from "./Tree/ResourceTokenCollection";
|
|||||||
import { ResourceTreeAdapter } from "./Tree/ResourceTreeAdapter";
|
import { ResourceTreeAdapter } from "./Tree/ResourceTreeAdapter";
|
||||||
import { ResourceTreeAdapterForResourceToken } from "./Tree/ResourceTreeAdapterForResourceToken";
|
import { ResourceTreeAdapterForResourceToken } from "./Tree/ResourceTreeAdapterForResourceToken";
|
||||||
import StoredProcedure from "./Tree/StoredProcedure";
|
import StoredProcedure from "./Tree/StoredProcedure";
|
||||||
|
import { useDatabases } from "./useDatabases";
|
||||||
|
import { useSelectedNode } from "./useSelectedNode";
|
||||||
|
|
||||||
BindingHandlersRegisterer.registerBindingHandlers();
|
BindingHandlersRegisterer.registerBindingHandlers();
|
||||||
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
|
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
|
||||||
var tmp = ComponentRegisterer;
|
var tmp = ComponentRegisterer;
|
||||||
|
|
||||||
export interface ExplorerParams {
|
export interface ExplorerParams {
|
||||||
setNotificationConsoleData: (consoleData: ConsoleData) => void;
|
|
||||||
setInProgressConsoleDataIdToBeDeleted: (id: string) => void;
|
|
||||||
tabsManager: TabsManager;
|
tabsManager: TabsManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Explorer {
|
export default class Explorer {
|
||||||
public collapsedResourceTreeWidth: number = ExplorerMetrics.CollapsedResourceTreeWidth;
|
|
||||||
|
|
||||||
public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>;
|
public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>;
|
||||||
public isServerlessEnabled: ko.Computed<boolean>;
|
|
||||||
public isAccountReady: ko.Observable<boolean>;
|
public isAccountReady: ko.Observable<boolean>;
|
||||||
public canSaveQueries: ko.Computed<boolean>;
|
|
||||||
public queriesClient: QueriesClient;
|
public queriesClient: QueriesClient;
|
||||||
public tableDataClient: TableDataClient;
|
public tableDataClient: TableDataClient;
|
||||||
public splitter: Splitter;
|
|
||||||
|
|
||||||
private setNotificationConsoleData: (consoleData: ConsoleData) => void;
|
|
||||||
private setInProgressConsoleDataIdToBeDeleted: (id: string) => void;
|
|
||||||
|
|
||||||
// Resource Tree
|
// Resource Tree
|
||||||
public databases: ko.ObservableArray<ViewModels.Database>;
|
|
||||||
public selectedDatabaseId: ko.Computed<string>;
|
|
||||||
public selectedCollectionId: ko.Computed<string>;
|
|
||||||
public selectedNode: ko.Observable<ViewModels.TreeNode>;
|
|
||||||
private resourceTree: ResourceTreeAdapter;
|
private resourceTree: ResourceTreeAdapter;
|
||||||
|
|
||||||
// Resource Token
|
// Resource Token
|
||||||
public resourceTokenDatabaseId: ko.Observable<string>;
|
|
||||||
public resourceTokenCollectionId: ko.Observable<string>;
|
|
||||||
public resourceTokenCollection: ko.Observable<ViewModels.CollectionBase>;
|
public resourceTokenCollection: ko.Observable<ViewModels.CollectionBase>;
|
||||||
public resourceTokenPartitionKey: ko.Observable<string>;
|
|
||||||
public isResourceTokenCollectionNodeSelected: ko.Computed<boolean>;
|
|
||||||
public resourceTreeForResourceToken: ResourceTreeAdapterForResourceToken;
|
public resourceTreeForResourceToken: ResourceTreeAdapterForResourceToken;
|
||||||
|
|
||||||
// Tabs
|
// Tabs
|
||||||
@@ -119,16 +95,12 @@ export default class Explorer {
|
|||||||
public tabsManager: TabsManager;
|
public tabsManager: TabsManager;
|
||||||
|
|
||||||
public gitHubOAuthService: GitHubOAuthService;
|
public gitHubOAuthService: GitHubOAuthService;
|
||||||
|
|
||||||
// features
|
|
||||||
public isHostedDataExplorerEnabled: ko.Computed<boolean>;
|
|
||||||
public isSchemaEnabled: ko.Computed<boolean>;
|
public isSchemaEnabled: ko.Computed<boolean>;
|
||||||
|
|
||||||
// Notebooks
|
// Notebooks
|
||||||
public isNotebookEnabled: ko.Observable<boolean>;
|
public isNotebookEnabled: ko.Observable<boolean>;
|
||||||
public isNotebooksEnabledForAccount: ko.Observable<boolean>;
|
public isNotebooksEnabledForAccount: ko.Observable<boolean>;
|
||||||
public notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>;
|
public notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>;
|
||||||
public notebookWorkspaceManager: NotebookWorkspaceManager;
|
|
||||||
public sparkClusterConnectionInfo: ko.Observable<DataModels.SparkClusterConnectionInfo>;
|
public sparkClusterConnectionInfo: ko.Observable<DataModels.SparkClusterConnectionInfo>;
|
||||||
public isSynapseLinkUpdating: ko.Observable<boolean>;
|
public isSynapseLinkUpdating: ko.Observable<boolean>;
|
||||||
public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>;
|
public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>;
|
||||||
@@ -146,9 +118,6 @@ export default class Explorer {
|
|||||||
private static readonly MaxNbDatabasesToAutoExpand = 5;
|
private static readonly MaxNbDatabasesToAutoExpand = 5;
|
||||||
|
|
||||||
constructor(params?: ExplorerParams) {
|
constructor(params?: ExplorerParams) {
|
||||||
this.setNotificationConsoleData = params?.setNotificationConsoleData;
|
|
||||||
this.setInProgressConsoleDataIdToBeDeleted = params?.setInProgressConsoleDataIdToBeDeleted;
|
|
||||||
|
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
@@ -163,8 +132,6 @@ export default class Explorer {
|
|||||||
userContext.authType === AuthType.ResourceToken
|
userContext.authType === AuthType.ResourceToken
|
||||||
? this.refreshDatabaseForResourceToken()
|
? this.refreshDatabaseForResourceToken()
|
||||||
: this.refreshAllDatabases(true);
|
: this.refreshAllDatabases(true);
|
||||||
RouteHandler.getInstance().initHandler();
|
|
||||||
this.notebookWorkspaceManager = new NotebookWorkspaceManager();
|
|
||||||
await this._refreshNotebooksEnabledStateForAccount();
|
await this._refreshNotebooksEnabledStateForAccount();
|
||||||
this.isNotebookEnabled(
|
this.isNotebookEnabled(
|
||||||
userContext.authType !== AuthType.ResourceToken &&
|
userContext.authType !== AuthType.ResourceToken &&
|
||||||
@@ -190,56 +157,13 @@ export default class Explorer {
|
|||||||
this.memoryUsageInfo = ko.observable<DataModels.MemoryUsageInfo>();
|
this.memoryUsageInfo = ko.observable<DataModels.MemoryUsageInfo>();
|
||||||
|
|
||||||
this.queriesClient = new QueriesClient(this);
|
this.queriesClient = new QueriesClient(this);
|
||||||
|
|
||||||
this.resourceTokenDatabaseId = ko.observable<string>();
|
|
||||||
this.resourceTokenCollectionId = ko.observable<string>();
|
|
||||||
this.resourceTokenCollection = ko.observable<ViewModels.CollectionBase>();
|
this.resourceTokenCollection = ko.observable<ViewModels.CollectionBase>();
|
||||||
this.resourceTokenPartitionKey = ko.observable<string>();
|
|
||||||
this.isSchemaEnabled = ko.computed<boolean>(() => userContext.features.enableSchema);
|
this.isSchemaEnabled = ko.computed<boolean>(() => userContext.features.enableSchema);
|
||||||
|
|
||||||
this.databases = ko.observableArray<ViewModels.Database>();
|
useSelectedNode.subscribe(() => {
|
||||||
this.canSaveQueries = ko.computed<boolean>(() => {
|
|
||||||
const savedQueriesDatabase: ViewModels.Database = _.find(
|
|
||||||
this.databases(),
|
|
||||||
(database: ViewModels.Database) => database.id() === Constants.SavedQueries.DatabaseName
|
|
||||||
);
|
|
||||||
if (!savedQueriesDatabase) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const savedQueriesCollection: ViewModels.Collection =
|
|
||||||
savedQueriesDatabase &&
|
|
||||||
_.find(
|
|
||||||
savedQueriesDatabase.collections(),
|
|
||||||
(collection: ViewModels.Collection) => collection.id() === Constants.SavedQueries.CollectionName
|
|
||||||
);
|
|
||||||
if (!savedQueriesCollection) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
this.selectedNode = ko.observable<ViewModels.TreeNode>();
|
|
||||||
this.selectedNode.subscribe((nodeSelected: ViewModels.TreeNode) => {
|
|
||||||
// Make sure switching tabs restores tabs display
|
// Make sure switching tabs restores tabs display
|
||||||
this.isTabsContentExpanded(false);
|
this.isTabsContentExpanded(false);
|
||||||
});
|
});
|
||||||
this.isResourceTokenCollectionNodeSelected = ko.computed<boolean>(() => {
|
|
||||||
return (
|
|
||||||
this.selectedNode() &&
|
|
||||||
this.resourceTokenCollection() &&
|
|
||||||
this.selectedNode().id() === this.resourceTokenCollection().id()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const splitterBounds: SplitterBounds = {
|
|
||||||
min: ExplorerMetrics.SplitterMinWidth,
|
|
||||||
max: ExplorerMetrics.SplitterMaxWidth,
|
|
||||||
};
|
|
||||||
this.splitter = new Splitter({
|
|
||||||
splitterId: "h_splitter1",
|
|
||||||
leftId: "resourcetree",
|
|
||||||
bounds: splitterBounds,
|
|
||||||
direction: SplitterDirection.Vertical,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.isFixedCollectionWithSharedThroughputSupported = ko.computed(() => {
|
this.isFixedCollectionWithSharedThroughputSupported = ko.computed(() => {
|
||||||
if (userContext.features.enableFixedCollectionWithSharedThroughput) {
|
if (userContext.features.enableFixedCollectionWithSharedThroughput) {
|
||||||
@@ -253,44 +177,10 @@ export default class Explorer {
|
|||||||
return isCapabilityEnabled("EnableMongo");
|
return isCapabilityEnabled("EnableMongo");
|
||||||
});
|
});
|
||||||
|
|
||||||
this.isServerlessEnabled = ko.computed(
|
|
||||||
() =>
|
|
||||||
userContext.databaseAccount?.properties?.capabilities?.find(
|
|
||||||
(item) => item.name === Constants.CapabilityNames.EnableServerless
|
|
||||||
) !== undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
this.isHostedDataExplorerEnabled = ko.computed<boolean>(
|
|
||||||
() =>
|
|
||||||
configContext.platform === Platform.Portal &&
|
|
||||||
!this.isRunningOnNationalCloud() &&
|
|
||||||
userContext.apiType !== "Gremlin"
|
|
||||||
);
|
|
||||||
this.selectedDatabaseId = ko.computed<string>(() => {
|
|
||||||
const selectedNode = this.selectedNode();
|
|
||||||
if (!selectedNode) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (selectedNode.nodeKind) {
|
|
||||||
case "Collection":
|
|
||||||
return (selectedNode as ViewModels.CollectionBase).databaseId || "";
|
|
||||||
case "Database":
|
|
||||||
return selectedNode.id() || "";
|
|
||||||
case "DocumentId":
|
|
||||||
case "StoredProcedure":
|
|
||||||
case "Trigger":
|
|
||||||
case "UserDefinedFunction":
|
|
||||||
return selectedNode.collection.databaseId || "";
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.tabsManager = params?.tabsManager ?? new TabsManager();
|
this.tabsManager = params?.tabsManager ?? new TabsManager();
|
||||||
this.tabsManager.openedTabs.subscribe((tabs) => {
|
this.tabsManager.openedTabs.subscribe((tabs) => {
|
||||||
if (tabs.length === 0) {
|
if (tabs.length === 0) {
|
||||||
this.selectedNode(undefined);
|
useSelectedNode.getState().setSelectedNode(undefined);
|
||||||
useCommandBar.getState().setContextButtons([]);
|
useCommandBar.getState().setContextButtons([]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -387,6 +277,7 @@ export default class Explorer {
|
|||||||
if (configContext.enableSchemaAnalyzer) {
|
if (configContext.enableSchemaAnalyzer) {
|
||||||
userContext.features.enableSchemaAnalyzer = true;
|
userContext.features.enableSchemaAnalyzer = true;
|
||||||
}
|
}
|
||||||
|
this.isAccountReady(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public openEnableSynapseLinkDialog(): void {
|
public openEnableSynapseLinkDialog(): void {
|
||||||
@@ -441,45 +332,17 @@ export default class Explorer {
|
|||||||
// TODO: return result
|
// TODO: return result
|
||||||
}
|
}
|
||||||
|
|
||||||
public isDatabaseNodeOrNoneSelected(): boolean {
|
public refreshDatabaseForResourceToken(): Promise<void> {
|
||||||
return this.isNoneSelected() || this.isDatabaseNodeSelected();
|
const databaseId = userContext.parsedResourceToken?.databaseId;
|
||||||
}
|
const collectionId = userContext.parsedResourceToken?.collectionId;
|
||||||
|
|
||||||
public isDatabaseNodeSelected(): boolean {
|
|
||||||
return (this.selectedNode() && this.selectedNode().nodeKind === "Database") || false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isNodeKindSelected(nodeKind: string): boolean {
|
|
||||||
return (this.selectedNode() && this.selectedNode().nodeKind === nodeKind) || false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isNoneSelected(): boolean {
|
|
||||||
return this.selectedNode() == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public logConsoleData(consoleData: ConsoleData): void {
|
|
||||||
this.setNotificationConsoleData(consoleData);
|
|
||||||
}
|
|
||||||
|
|
||||||
public deleteInProgressConsoleDataWithId(id: string): void {
|
|
||||||
this.setInProgressConsoleDataIdToBeDeleted(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public refreshDatabaseForResourceToken(): Q.Promise<any> {
|
|
||||||
const databaseId = this.resourceTokenDatabaseId();
|
|
||||||
const collectionId = this.resourceTokenCollectionId();
|
|
||||||
if (!databaseId || !collectionId) {
|
if (!databaseId || !collectionId) {
|
||||||
return Q.reject();
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
|
|
||||||
const deferred: Q.Deferred<void> = Q.defer();
|
return readCollection(databaseId, collectionId).then((collection: DataModels.Collection) => {
|
||||||
readCollection(databaseId, collectionId).then((collection: DataModels.Collection) => {
|
|
||||||
this.resourceTokenCollection(new ResourceTokenCollection(this, databaseId, collection));
|
this.resourceTokenCollection(new ResourceTokenCollection(this, databaseId, collection));
|
||||||
this.selectedNode(this.resourceTokenCollection());
|
useSelectedNode.getState().setSelectedNode(this.resourceTokenCollection());
|
||||||
deferred.resolve();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public refreshAllDatabases(isInitialLoad?: boolean): Q.Promise<any> {
|
public refreshAllDatabases(isInitialLoad?: boolean): Q.Promise<any> {
|
||||||
@@ -504,11 +367,9 @@ export default class Explorer {
|
|||||||
},
|
},
|
||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
const currentlySelectedNode: ViewModels.TreeNode = this.selectedNode();
|
|
||||||
const deltaDatabases = this.getDeltaDatabases(databases);
|
const deltaDatabases = this.getDeltaDatabases(databases);
|
||||||
this.addDatabasesToList(deltaDatabases.toAdd);
|
this.addDatabasesToList(deltaDatabases.toAdd);
|
||||||
this.deleteDatabasesFromList(deltaDatabases.toDelete);
|
this.deleteDatabasesFromList(deltaDatabases.toDelete);
|
||||||
this.selectedNode(currentlySelectedNode);
|
|
||||||
this.refreshAndExpandNewDatabases(deltaDatabases.toAdd).then(
|
this.refreshAndExpandNewDatabases(deltaDatabases.toAdd).then(
|
||||||
() => {
|
() => {
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
@@ -597,37 +458,19 @@ export default class Explorer {
|
|||||||
this._isInitializingNotebooks = true;
|
this._isInitializingNotebooks = true;
|
||||||
|
|
||||||
await this.ensureNotebookWorkspaceRunning();
|
await this.ensureNotebookWorkspaceRunning();
|
||||||
let connectionInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
const connectionInfo = await listConnectionInfo(
|
||||||
authToken: undefined,
|
userContext.subscriptionId,
|
||||||
notebookServerEndpoint: undefined,
|
userContext.resourceGroup,
|
||||||
};
|
databaseAccount.name,
|
||||||
try {
|
|
||||||
connectionInfo = await this.notebookWorkspaceManager.getNotebookConnectionInfoAsync(
|
|
||||||
databaseAccount.id,
|
|
||||||
"default"
|
"default"
|
||||||
);
|
);
|
||||||
} catch (error) {
|
|
||||||
this._isInitializingNotebooks = false;
|
|
||||||
handleError(
|
|
||||||
error,
|
|
||||||
"initNotebooks/getNotebookConnectionInfoAsync",
|
|
||||||
`Failed to get notebook workspace connection info: ${getErrorMessage(error)}`
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
// Overwrite with feature flags
|
|
||||||
if (userContext.features.notebookServerUrl) {
|
|
||||||
connectionInfo.notebookServerEndpoint = userContext.features.notebookServerUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userContext.features.notebookServerToken) {
|
this.notebookServerInfo({
|
||||||
connectionInfo.authToken = userContext.features.notebookServerToken;
|
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.notebookServerEndpoint,
|
||||||
}
|
authToken: userContext.features.notebookServerToken || connectionInfo.authToken,
|
||||||
|
});
|
||||||
this.notebookServerInfo(connectionInfo);
|
|
||||||
this.notebookServerInfo.valueHasMutated();
|
this.notebookServerInfo.valueHasMutated();
|
||||||
this.refreshNotebookList();
|
this.refreshNotebookList();
|
||||||
}
|
|
||||||
|
|
||||||
this._isInitializingNotebooks = false;
|
this._isInitializingNotebooks = false;
|
||||||
}
|
}
|
||||||
@@ -659,7 +502,11 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const workspaces = await this.notebookWorkspaceManager.getNotebookWorkspacesAsync(databaseAccount?.id);
|
const { value: workspaces } = await listByDatabaseAccount(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name
|
||||||
|
);
|
||||||
return workspaces && workspaces.length > 0 && workspaces.some((workspace) => workspace.name === "default");
|
return workspaces && workspaces.length > 0 && workspaces.some((workspace) => workspace.name === "default");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.logError(getErrorMessage(error), "Explorer/_containsDefaultNotebookWorkspace");
|
Logger.logError(getErrorMessage(error), "Explorer/_containsDefaultNotebookWorkspace");
|
||||||
@@ -674,8 +521,10 @@ export default class Explorer {
|
|||||||
|
|
||||||
let clearMessage;
|
let clearMessage;
|
||||||
try {
|
try {
|
||||||
const notebookWorkspace = await this.notebookWorkspaceManager.getNotebookWorkspaceAsync(
|
const notebookWorkspace = await getWorkspace(
|
||||||
userContext.databaseAccount.id,
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
"default"
|
"default"
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
@@ -685,7 +534,7 @@ export default class Explorer {
|
|||||||
notebookWorkspace.properties.status.toLowerCase() === "stopped"
|
notebookWorkspace.properties.status.toLowerCase() === "stopped"
|
||||||
) {
|
) {
|
||||||
clearMessage = NotificationConsoleUtils.logConsoleProgress("Initializing notebook workspace");
|
clearMessage = NotificationConsoleUtils.logConsoleProgress("Initializing notebook workspace");
|
||||||
await this.notebookWorkspaceManager.startNotebookWorkspaceAsync(userContext.databaseAccount.id, "default");
|
await start(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, "default");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, "Explorer/ensureNotebookWorkspaceRunning", "Failed to initialize notebook workspace");
|
handleError(error, "Explorer/ensureNotebookWorkspaceRunning", "Failed to initialize notebook workspace");
|
||||||
@@ -713,73 +562,6 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public findSelectedDatabase(): ViewModels.Database {
|
|
||||||
if (!this.selectedNode()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (this.selectedNode().nodeKind === "Database") {
|
|
||||||
return _.find(this.databases(), (database: ViewModels.Database) => database.id() === this.selectedNode().id());
|
|
||||||
}
|
|
||||||
return this.findSelectedCollection().database;
|
|
||||||
}
|
|
||||||
|
|
||||||
public findDatabaseWithId(databaseId: string): ViewModels.Database {
|
|
||||||
return _.find(this.databases(), (database: ViewModels.Database) => database.id() === databaseId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public isLastNonEmptyDatabase(): boolean {
|
|
||||||
if (
|
|
||||||
this.isLastDatabase() &&
|
|
||||||
this.databases()[0] &&
|
|
||||||
this.databases()[0].collections &&
|
|
||||||
this.databases()[0].collections().length > 0
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isLastDatabase(): boolean {
|
|
||||||
if (this.databases().length > 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isSelectedDatabaseShared(): boolean {
|
|
||||||
const database = this.findSelectedDatabase();
|
|
||||||
if (!!database) {
|
|
||||||
return database.offer && !!database.offer();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public configure(inputs: ViewModels.DataExplorerInputsFrame): void {
|
|
||||||
if (inputs != null) {
|
|
||||||
// In development mode, save the iframe message from the portal in session storage.
|
|
||||||
// This allows webpack hot reload to funciton properly
|
|
||||||
if (process.env.NODE_ENV === "development") {
|
|
||||||
sessionStorage.setItem("portalDataExplorerInitMessage", JSON.stringify(inputs));
|
|
||||||
}
|
|
||||||
this.isAccountReady(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public findSelectedCollection(): ViewModels.Collection {
|
|
||||||
return (this.selectedNode().nodeKind === "Collection"
|
|
||||||
? this.selectedNode()
|
|
||||||
: this.selectedNode().collection) as ViewModels.Collection;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isRunningOnNationalCloud(): boolean {
|
|
||||||
return (
|
|
||||||
userContext.portalEnv === "blackforest" ||
|
|
||||||
userContext.portalEnv === "fairfax" ||
|
|
||||||
userContext.portalEnv === "mooncake"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private refreshAndExpandNewDatabases(newDatabases: ViewModels.Database[]): Q.Promise<void> {
|
private refreshAndExpandNewDatabases(newDatabases: ViewModels.Database[]): Q.Promise<void> {
|
||||||
// we reload collections for all databases so the resource tree reflects any collection-level changes
|
// we reload collections for all databases so the resource tree reflects any collection-level changes
|
||||||
// i.e addition of stored procedures, etc.
|
// i.e addition of stored procedures, etc.
|
||||||
@@ -787,10 +569,11 @@ export default class Explorer {
|
|||||||
let loadCollectionPromises: Q.Promise<void>[] = [];
|
let loadCollectionPromises: Q.Promise<void>[] = [];
|
||||||
|
|
||||||
// If the user has a lot of databases, only load expanded databases.
|
// If the user has a lot of databases, only load expanded databases.
|
||||||
|
const databases = useDatabases.getState().databases;
|
||||||
const databasesToLoad =
|
const databasesToLoad =
|
||||||
this.databases().length <= Explorer.MaxNbDatabasesToAutoExpand
|
databases.length <= Explorer.MaxNbDatabasesToAutoExpand
|
||||||
? this.databases()
|
? databases
|
||||||
: this.databases().filter((db) => db.isDatabaseExpanded());
|
: databases.filter((db) => db.isDatabaseExpanded() || db.id() === Constants.SavedQueries.DatabaseName);
|
||||||
|
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.LoadCollections, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.LoadCollections, {
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
@@ -835,37 +618,16 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public findCollection(databaseId: string, collectionId: string): ViewModels.Collection {
|
|
||||||
const database: ViewModels.Database = this.databases().find(
|
|
||||||
(database: ViewModels.Database) => database.id() === databaseId
|
|
||||||
);
|
|
||||||
return database?.collections().find((collection: ViewModels.Collection) => collection.id() === collectionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public isLastCollection(): boolean {
|
|
||||||
let collectionCount = 0;
|
|
||||||
if (this.databases().length == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (let i = 0; i < this.databases().length; i++) {
|
|
||||||
const database = this.databases()[i];
|
|
||||||
collectionCount += database.collections().length;
|
|
||||||
if (collectionCount > 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getDeltaDatabases(
|
private getDeltaDatabases(
|
||||||
updatedDatabaseList: DataModels.Database[]
|
updatedDatabaseList: DataModels.Database[]
|
||||||
): {
|
): {
|
||||||
toAdd: ViewModels.Database[];
|
toAdd: ViewModels.Database[];
|
||||||
toDelete: ViewModels.Database[];
|
toDelete: ViewModels.Database[];
|
||||||
} {
|
} {
|
||||||
|
const databases = useDatabases.getState().databases;
|
||||||
const newDatabases: DataModels.Database[] = _.filter(updatedDatabaseList, (database: DataModels.Database) => {
|
const newDatabases: DataModels.Database[] = _.filter(updatedDatabaseList, (database: DataModels.Database) => {
|
||||||
const databaseExists = _.some(
|
const databaseExists = _.some(
|
||||||
this.databases(),
|
databases,
|
||||||
(existingDatabase: ViewModels.Database) => existingDatabase.id() === database.id
|
(existingDatabase: ViewModels.Database) => existingDatabase.id() === database.id
|
||||||
);
|
);
|
||||||
return !databaseExists;
|
return !databaseExists;
|
||||||
@@ -875,7 +637,7 @@ export default class Explorer {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let databasesToDelete: ViewModels.Database[] = [];
|
let databasesToDelete: ViewModels.Database[] = [];
|
||||||
ko.utils.arrayForEach(this.databases(), (database: ViewModels.Database) => {
|
ko.utils.arrayForEach(databases, (database: ViewModels.Database) => {
|
||||||
const databasePresentInUpdatedList = _.some(
|
const databasePresentInUpdatedList = _.some(
|
||||||
updatedDatabaseList,
|
updatedDatabaseList,
|
||||||
(db: DataModels.Database) => db.id === database.id()
|
(db: DataModels.Database) => db.id === database.id()
|
||||||
@@ -889,24 +651,12 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private addDatabasesToList(databases: ViewModels.Database[]): void {
|
private addDatabasesToList(databases: ViewModels.Database[]): void {
|
||||||
this.databases(
|
useDatabases.getState().addDatabases(databases);
|
||||||
this.databases()
|
|
||||||
.concat(databases)
|
|
||||||
.sort((database1, database2) => database1.id().localeCompare(database2.id()))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private deleteDatabasesFromList(databasesToRemove: ViewModels.Database[]): void {
|
private deleteDatabasesFromList(databasesToRemove: ViewModels.Database[]): void {
|
||||||
const databasesToKeep: ViewModels.Database[] = [];
|
const deleteDatabase = useDatabases.getState().deleteDatabase;
|
||||||
|
databasesToRemove.forEach((database) => deleteDatabase(database));
|
||||||
ko.utils.arrayForEach(this.databases(), (database: ViewModels.Database) => {
|
|
||||||
const shouldRemoveDatabase = _.some(databasesToRemove, (db: ViewModels.Database) => db.id === database.id);
|
|
||||||
if (!shouldRemoveDatabase) {
|
|
||||||
databasesToKeep.push(database);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.databases(databasesToKeep);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public uploadFile(name: string, content: string, parent: NotebookContentItem): Promise<NotebookContentItem> {
|
public uploadFile(name: string, content: string, parent: NotebookContentItem): Promise<NotebookContentItem> {
|
||||||
@@ -1068,7 +818,6 @@ export default class Explorer {
|
|||||||
tabPath: notebookContentItem.path,
|
tabPath: notebookContentItem.path,
|
||||||
collection: null,
|
collection: null,
|
||||||
masterKey: userContext.masterKey || "",
|
masterKey: userContext.masterKey || "",
|
||||||
hashLocation: "notebooks",
|
|
||||||
isTabsContentExpanded: ko.observable(true),
|
isTabsContentExpanded: ko.observable(true),
|
||||||
onLoadStartKey: null,
|
onLoadStartKey: null,
|
||||||
container: this,
|
container: this,
|
||||||
@@ -1368,33 +1117,34 @@ export default class Explorer {
|
|||||||
|
|
||||||
public openNotebookTerminal(kind: ViewModels.TerminalKind) {
|
public openNotebookTerminal(kind: ViewModels.TerminalKind) {
|
||||||
let title: string;
|
let title: string;
|
||||||
let hashLocation: string;
|
|
||||||
|
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case ViewModels.TerminalKind.Default:
|
case ViewModels.TerminalKind.Default:
|
||||||
title = "Terminal";
|
title = "Terminal";
|
||||||
hashLocation = "terminal";
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ViewModels.TerminalKind.Mongo:
|
case ViewModels.TerminalKind.Mongo:
|
||||||
title = "Mongo Shell";
|
title = "Mongo Shell";
|
||||||
hashLocation = "mongo-shell";
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ViewModels.TerminalKind.Cassandra:
|
case ViewModels.TerminalKind.Cassandra:
|
||||||
title = "Cassandra Shell";
|
title = "Cassandra Shell";
|
||||||
hashLocation = "cassandra-shell";
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error("Terminal kind: ${kind} not supported");
|
throw new Error("Terminal kind: ${kind} not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
const terminalTabs: TerminalTab[] = this.tabsManager.getTabs(ViewModels.CollectionTabKind.Terminal, (tab) =>
|
const terminalTabs: TerminalTab[] = this.tabsManager.getTabs(
|
||||||
tab.hashLocation().startsWith(hashLocation)
|
ViewModels.CollectionTabKind.Terminal,
|
||||||
|
(tab) => tab.tabTitle() === title
|
||||||
) as TerminalTab[];
|
) as TerminalTab[];
|
||||||
|
|
||||||
const index = terminalTabs.length + 1;
|
let index = 1;
|
||||||
|
if (terminalTabs.length > 0) {
|
||||||
|
index = terminalTabs[terminalTabs.length - 1].index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
const newTab = new TerminalTab({
|
const newTab = new TerminalTab({
|
||||||
account: userContext.databaseAccount,
|
account: userContext.databaseAccount,
|
||||||
tabKind: ViewModels.CollectionTabKind.Terminal,
|
tabKind: ViewModels.CollectionTabKind.Terminal,
|
||||||
@@ -1402,11 +1152,11 @@ export default class Explorer {
|
|||||||
title: `${title} ${index}`,
|
title: `${title} ${index}`,
|
||||||
tabPath: `${title} ${index}`,
|
tabPath: `${title} ${index}`,
|
||||||
collection: null,
|
collection: null,
|
||||||
hashLocation: `${hashLocation} ${index}`,
|
|
||||||
isTabsContentExpanded: ko.observable(true),
|
isTabsContentExpanded: ko.observable(true),
|
||||||
onLoadStartKey: null,
|
onLoadStartKey: null,
|
||||||
container: this,
|
container: this,
|
||||||
kind: kind,
|
kind: kind,
|
||||||
|
index: index,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.tabsManager.activateNewTab(newTab);
|
this.tabsManager.activateNewTab(newTab);
|
||||||
@@ -1419,11 +1169,10 @@ export default class Explorer {
|
|||||||
isFavorite?: boolean
|
isFavorite?: boolean
|
||||||
) {
|
) {
|
||||||
const title = "Gallery";
|
const title = "Gallery";
|
||||||
const hashLocation = "gallery";
|
|
||||||
const GalleryTab = await (await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab")).default;
|
const GalleryTab = await (await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab")).default;
|
||||||
const galleryTab = this.tabsManager
|
const galleryTab = this.tabsManager
|
||||||
.getTabs(ViewModels.CollectionTabKind.Gallery)
|
.getTabs(ViewModels.CollectionTabKind.Gallery)
|
||||||
.find((tab) => tab.hashLocation() == hashLocation);
|
.find((tab) => tab.tabTitle() == title);
|
||||||
|
|
||||||
if (galleryTab instanceof GalleryTab) {
|
if (galleryTab instanceof GalleryTab) {
|
||||||
this.tabsManager.activateTab(galleryTab);
|
this.tabsManager.activateTab(galleryTab);
|
||||||
@@ -1432,9 +1181,8 @@ export default class Explorer {
|
|||||||
new GalleryTab(
|
new GalleryTab(
|
||||||
{
|
{
|
||||||
tabKind: ViewModels.CollectionTabKind.Gallery,
|
tabKind: ViewModels.CollectionTabKind.Gallery,
|
||||||
title: title,
|
title,
|
||||||
tabPath: title,
|
tabPath: title,
|
||||||
hashLocation: hashLocation,
|
|
||||||
onLoadStartKey: null,
|
onLoadStartKey: null,
|
||||||
isTabsContentExpanded: ko.observable(true),
|
isTabsContentExpanded: ko.observable(true),
|
||||||
},
|
},
|
||||||
@@ -1452,11 +1200,19 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onNewCollectionClicked(databaseId?: string): void {
|
public async onNewCollectionClicked(databaseId?: string): Promise<void> {
|
||||||
if (userContext.apiType === "Cassandra") {
|
if (userContext.apiType === "Cassandra") {
|
||||||
this.openCassandraAddCollectionPane();
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel(
|
||||||
|
"Add Table",
|
||||||
|
<CassandraAddCollectionPane explorer={this} cassandraApiClient={new CassandraAPIDataClient()} />
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this.openAddCollectionPanel(databaseId);
|
await useDatabases.getState().loadDatabaseOffers();
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel("New " + getCollectionName(), <AddCollectionPanel explorer={this} databaseId={databaseId} />);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1500,50 +1256,8 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async loadDatabaseOffers(): Promise<void> {
|
|
||||||
await Promise.all(
|
|
||||||
this.databases()?.map(async (database: ViewModels.Database) => {
|
|
||||||
await database.loadOffer();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public isFirstResourceCreated(): boolean {
|
|
||||||
const databases: ViewModels.Database[] = this.databases();
|
|
||||||
|
|
||||||
if (!databases || databases.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return databases.some((database) => {
|
|
||||||
// user has created at least one collection
|
|
||||||
if (database.collections()?.length > 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// user has created a database with shared throughput
|
|
||||||
if (database.offer()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// use has created an empty database without shared throughput
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
public openDeleteCollectionConfirmationPane(): void {
|
|
||||||
useSidePanel
|
|
||||||
.getState()
|
|
||||||
.openSidePanel("Delete " + getCollectionName(), <DeleteCollectionConfirmationPane explorer={this} />);
|
|
||||||
}
|
|
||||||
|
|
||||||
public openDeleteDatabaseConfirmationPane(): void {
|
|
||||||
useSidePanel
|
|
||||||
.getState()
|
|
||||||
.openSidePanel(
|
|
||||||
"Delete " + getDatabaseName(),
|
|
||||||
<DeleteDatabaseConfirmationPanel explorer={this} selectedDatabase={this.findSelectedDatabase()} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
public openUploadItemsPanePane(): void {
|
public openUploadItemsPanePane(): void {
|
||||||
useSidePanel.getState().openSidePanel("Upload " + getUploadName(), <UploadItemsPane explorer={this} />);
|
useSidePanel.getState().openSidePanel("Upload " + getUploadName(), <UploadItemsPane />);
|
||||||
}
|
}
|
||||||
public openExecuteSprocParamsPanel(storedProcedure: StoredProcedure): void {
|
public openExecuteSprocParamsPanel(storedProcedure: StoredProcedure): void {
|
||||||
useSidePanel
|
useSidePanel
|
||||||
@@ -1551,12 +1265,6 @@ export default class Explorer {
|
|||||||
.openSidePanel("Input parameters", <ExecuteSprocParamsPane storedProcedure={storedProcedure} />);
|
.openSidePanel("Input parameters", <ExecuteSprocParamsPane storedProcedure={storedProcedure} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async openAddCollectionPanel(databaseId?: string): Promise<void> {
|
|
||||||
await this.loadDatabaseOffers();
|
|
||||||
useSidePanel
|
|
||||||
.getState()
|
|
||||||
.openSidePanel("New " + getCollectionName(), <AddCollectionPanel explorer={this} databaseId={databaseId} />);
|
|
||||||
}
|
|
||||||
public openAddDatabasePane(): void {
|
public openAddDatabasePane(): void {
|
||||||
useSidePanel.getState().openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={this} />);
|
useSidePanel.getState().openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={this} />);
|
||||||
}
|
}
|
||||||
@@ -1579,14 +1287,6 @@ export default class Explorer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public openCassandraAddCollectionPane(): void {
|
|
||||||
useSidePanel
|
|
||||||
.getState()
|
|
||||||
.openSidePanel(
|
|
||||||
"Add Table",
|
|
||||||
<CassandraAddCollectionPane explorer={this} cassandraApiClient={new CassandraAPIDataClient()} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
public openGitHubReposPanel(header: string, junoClient?: JunoClient): void {
|
public openGitHubReposPanel(header: string, junoClient?: JunoClient): void {
|
||||||
useSidePanel
|
useSidePanel
|
||||||
.getState()
|
.getState()
|
||||||
@@ -1600,43 +1300,9 @@ export default class Explorer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public openAddTableEntityPanel(queryTablesTab: QueryTablesTab, tableEntityListViewModel: TableListViewModal): void {
|
|
||||||
useSidePanel
|
|
||||||
.getState()
|
|
||||||
.openSidePanel(
|
|
||||||
"Add Table Entity",
|
|
||||||
<AddTableEntityPanel
|
|
||||||
tableDataClient={this.tableDataClient}
|
|
||||||
queryTablesTab={queryTablesTab}
|
|
||||||
tableEntityListViewModel={tableEntityListViewModel}
|
|
||||||
cassandraApiClient={new CassandraAPIDataClient()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
public openSetupNotebooksPanel(title: string, description: string): void {
|
public openSetupNotebooksPanel(title: string, description: string): void {
|
||||||
useSidePanel
|
useSidePanel
|
||||||
.getState()
|
.getState()
|
||||||
.openSidePanel(title, <SetupNoteBooksPanel explorer={this} panelTitle={title} panelDescription={description} />);
|
.openSidePanel(title, <SetupNoteBooksPanel explorer={this} panelTitle={title} panelDescription={description} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
public openEditTableEntityPanel(queryTablesTab: QueryTablesTab, tableEntityListViewModel: TableListViewModal): void {
|
|
||||||
useSidePanel
|
|
||||||
.getState()
|
|
||||||
.openSidePanel(
|
|
||||||
"Edit Table Entity",
|
|
||||||
<EditTableEntityPanel
|
|
||||||
tableDataClient={this.tableDataClient}
|
|
||||||
queryTablesTab={queryTablesTab}
|
|
||||||
tableEntityListViewModel={tableEntityListViewModel}
|
|
||||||
cassandraApiClient={new CassandraAPIDataClient()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public openTableSelectQueryPanel(queryViewModal: QueryViewModel): void {
|
|
||||||
useSidePanel.getState().openSidePanel("Select Column", <TableQuerySelectPanel queryViewModel={queryViewModal} />);
|
|
||||||
}
|
|
||||||
public openSettingPane(): void {
|
|
||||||
useSidePanel.getState().openSidePanel("Settings", <SettingsPane />);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { queryDocumentsPage } from "../../../Common/dataAccess/queryDocumentsPag
|
|||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import * as StorageUtility from "../../../Shared/StorageUtility";
|
import * as StorageUtility from "../../../Shared/StorageUtility";
|
||||||
import { TabComponent } from "../../Controls/Tabs/TabComponent";
|
import { TabComponent } from "../../Controls/Tabs/TabComponent";
|
||||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../../Menus/NotificationConsole/ConsoleData";
|
||||||
import GraphTab from "../../Tabs/GraphTab";
|
import GraphTab from "../../Tabs/GraphTab";
|
||||||
import * as D3ForceGraph from "./D3ForceGraph";
|
import * as D3ForceGraph from "./D3ForceGraph";
|
||||||
import { GraphData } from "./GraphData";
|
import { GraphData } from "./GraphData";
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Ut
|
|||||||
import { EditorReact } from "../../Controls/Editor/EditorReact";
|
import { EditorReact } from "../../Controls/Editor/EditorReact";
|
||||||
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
|
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
|
||||||
import * as TabComponent from "../../Controls/Tabs/TabComponent";
|
import * as TabComponent from "../../Controls/Tabs/TabComponent";
|
||||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../../Menus/NotificationConsole/ConsoleData";
|
||||||
import { IGraphConfig } from "../../Tabs/GraphTab";
|
import { IGraphConfig } from "../../Tabs/GraphTab";
|
||||||
import { ArraysByKeyCache } from "./ArraysByKeyCache";
|
import { ArraysByKeyCache } from "./ArraysByKeyCache";
|
||||||
import * as D3ForceGraph from "./D3ForceGraph";
|
import * as D3ForceGraph from "./D3ForceGraph";
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
import * as React from "react";
|
|
||||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
|
||||||
import { IGraphConfig } from "../../Tabs/GraphTab";
|
|
||||||
import { GraphAccessor, GraphExplorer } from "./GraphExplorer";
|
|
||||||
interface Parameter {
|
|
||||||
onIsNewVertexDisabledChange: (isEnabled: boolean) => void;
|
|
||||||
onGraphAccessorCreated: (instance: GraphAccessor) => void;
|
|
||||||
onIsFilterQueryLoading: (isFilterQueryLoading: boolean) => void;
|
|
||||||
onIsValidQuery: (isValidQuery: boolean) => void;
|
|
||||||
onIsPropertyEditing: (isEditing: boolean) => void;
|
|
||||||
onIsGraphDisplayed: (isDisplayed: boolean) => void;
|
|
||||||
onResetDefaultGraphConfigValues: () => void;
|
|
||||||
|
|
||||||
collectionPartitionKeyProperty: string;
|
|
||||||
graphBackendEndpoint: string;
|
|
||||||
databaseId: string;
|
|
||||||
collectionId: string;
|
|
||||||
masterKey: string;
|
|
||||||
|
|
||||||
onLoadStartKey: number;
|
|
||||||
onLoadStartKeyChange: (newKey: number) => void;
|
|
||||||
resourceId: string;
|
|
||||||
|
|
||||||
igraphConfigUiData: ViewModels.IGraphConfigUiData;
|
|
||||||
igraphConfig: IGraphConfig;
|
|
||||||
setIConfigUiData?: (data: string[]) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IGraphExplorerProps {
|
|
||||||
isChanged: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IGraphExplorerStates {
|
|
||||||
isChangedState: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GraphExplorerAdapter
|
|
||||||
extends ReactAdapter,
|
|
||||||
React.Component<IGraphExplorerProps, IGraphExplorerStates> {}
|
|
||||||
export class GraphExplorerAdapter implements ReactAdapter {
|
|
||||||
public params: Parameter;
|
|
||||||
public parameters = {};
|
|
||||||
public isNewVertexDisabled: boolean;
|
|
||||||
|
|
||||||
public constructor(params: Parameter, props?: IGraphExplorerProps) {
|
|
||||||
this.params = params;
|
|
||||||
}
|
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<GraphExplorer
|
|
||||||
onIsNewVertexDisabledChange={this.params.onIsNewVertexDisabledChange}
|
|
||||||
onGraphAccessorCreated={this.params.onGraphAccessorCreated}
|
|
||||||
onIsFilterQueryLoadingChange={this.params.onIsFilterQueryLoading}
|
|
||||||
onIsValidQueryChange={this.params.onIsValidQuery}
|
|
||||||
onIsPropertyEditing={this.params.onIsPropertyEditing}
|
|
||||||
onIsGraphDisplayed={this.params.onIsGraphDisplayed}
|
|
||||||
onResetDefaultGraphConfigValues={this.params.onResetDefaultGraphConfigValues}
|
|
||||||
collectionPartitionKeyProperty={this.params.collectionPartitionKeyProperty}
|
|
||||||
graphBackendEndpoint={this.params.graphBackendEndpoint}
|
|
||||||
databaseId={this.params.databaseId}
|
|
||||||
collectionId={this.params.collectionId}
|
|
||||||
masterKey={this.params.masterKey}
|
|
||||||
onLoadStartKey={this.params.onLoadStartKey}
|
|
||||||
onLoadStartKeyChange={this.params.onLoadStartKeyChange}
|
|
||||||
resourceId={this.params.resourceId}
|
|
||||||
igraphConfigUiData={this.params.igraphConfigUiData}
|
|
||||||
igraphConfig={this.params.igraphConfig}
|
|
||||||
setIConfigUiData={this.params.setIConfigUiData}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
import * as GraphUtil from "./GraphUtil";
|
|
||||||
import { GraphData, GremlinVertex, GremlinEdge } from "./GraphData";
|
|
||||||
import * as sinon from "sinon";
|
import * as sinon from "sinon";
|
||||||
|
import { GraphData, GremlinEdge, GremlinVertex } from "./GraphData";
|
||||||
import { GraphExplorer } from "./GraphExplorer";
|
import { GraphExplorer } from "./GraphExplorer";
|
||||||
window.$ = window.jQuery = require("jquery");
|
import * as GraphUtil from "./GraphUtil";
|
||||||
|
|
||||||
const OUT_E_MATCHER = "g\\.V\\(.*\\).outE\\(\\).*\\.as\\('e'\\).inV\\(\\)\\.as\\('v'\\)\\.select\\('e', *'v'\\)";
|
const OUT_E_MATCHER = "g\\.V\\(.*\\).outE\\(\\).*\\.as\\('e'\\).inV\\(\\)\\.as\\('v'\\)\\.select\\('e', *'v'\\)";
|
||||||
const IN_E_MATCHER = "g\\.V\\(.*\\).inE\\(\\).*\\.as\\('e'\\).outV\\(\\)\\.as\\('v'\\)\\.select\\('e', *'v'\\)";
|
const IN_E_MATCHER = "g\\.V\\(.*\\).inE\\(\\).*\\.as\\('e'\\).outV\\(\\)\\.as\\('v'\\)\\.select\\('e', *'v'\\)";
|
||||||
|
|
||||||
|
|||||||
@@ -5,21 +5,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { GraphHighlightedNodeData, EditedProperties, EditedEdges, PossibleVertex } from "./GraphExplorer";
|
|
||||||
import { CollapsiblePanel } from "../../Controls/CollapsiblePanel/CollapsiblePanel";
|
|
||||||
import { ReadOnlyNodePropertiesComponent } from "./ReadOnlyNodePropertiesComponent";
|
|
||||||
import { EditorNodePropertiesComponent } from "./EditorNodePropertiesComponent";
|
|
||||||
import { ReadOnlyNeighborsComponent } from "./ReadOnlyNeighborsComponent";
|
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
|
||||||
import { Item } from "../../Controls/InputTypeahead/InputTypeaheadComponent";
|
|
||||||
import * as EditorNeighbors from "./EditorNeighborsComponent";
|
|
||||||
import EditIcon from "../../../../images/edit.svg";
|
|
||||||
import DeleteIcon from "../../../../images/delete.svg";
|
|
||||||
import CheckIcon from "../../../../images/check.svg";
|
|
||||||
import CancelIcon from "../../../../images/cancel.svg";
|
import CancelIcon from "../../../../images/cancel.svg";
|
||||||
import { GraphExplorer } from "./GraphExplorer";
|
import CheckIcon from "../../../../images/check.svg";
|
||||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
import DeleteIcon from "../../../../images/delete.svg";
|
||||||
|
import EditIcon from "../../../../images/edit.svg";
|
||||||
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
|
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
|
||||||
|
import { CollapsiblePanel } from "../../Controls/CollapsiblePanel/CollapsiblePanel";
|
||||||
|
import { Item } from "../../Controls/InputTypeahead/InputTypeaheadComponent";
|
||||||
|
import { ConsoleDataType } from "../../Menus/NotificationConsole/ConsoleData";
|
||||||
|
import * as EditorNeighbors from "./EditorNeighborsComponent";
|
||||||
|
import { EditorNodePropertiesComponent } from "./EditorNodePropertiesComponent";
|
||||||
|
import {
|
||||||
|
EditedEdges,
|
||||||
|
EditedProperties,
|
||||||
|
GraphExplorer,
|
||||||
|
GraphHighlightedNodeData,
|
||||||
|
PossibleVertex,
|
||||||
|
} from "./GraphExplorer";
|
||||||
|
import { ReadOnlyNeighborsComponent } from "./ReadOnlyNeighborsComponent";
|
||||||
|
import { ReadOnlyNodePropertiesComponent } from "./ReadOnlyNodePropertiesComponent";
|
||||||
|
|
||||||
export enum Mode {
|
export enum Mode {
|
||||||
READONLY_PROP,
|
READONLY_PROP,
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import * as React from "react";
|
|||||||
import create, { UseStore } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { StyleConstants } from "../../../Common/Constants";
|
import { StyleConstants } from "../../../Common/Constants";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import { useObservable } from "../../../hooks/useObservable";
|
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
||||||
import * as CommandBarUtil from "./CommandBarUtil";
|
import * as CommandBarUtil from "./CommandBarUtil";
|
||||||
|
|
||||||
@@ -29,13 +29,13 @@ export const useCommandBar: UseStore<CommandBarStore> = create((set) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
||||||
useObservable(container.selectedNode);
|
const selectedNodeState = useSelectedNode();
|
||||||
const buttons = useCommandBar((state) => state.contextButtons);
|
const buttons = useCommandBar((state) => state.contextButtons);
|
||||||
const backgroundColor = StyleConstants.BaseLight;
|
const backgroundColor = StyleConstants.BaseLight;
|
||||||
|
|
||||||
const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(container);
|
const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(container, selectedNodeState);
|
||||||
const contextButtons = (buttons || []).concat(
|
const contextButtons = (buttons || []).concat(
|
||||||
CommandBarComponentButtonFactory.createContextCommandBarButtons(container)
|
CommandBarComponentButtonFactory.createContextCommandBarButtons(container, selectedNodeState)
|
||||||
);
|
);
|
||||||
const controlButtons = CommandBarComponentButtonFactory.createControlCommandBarButtons(container);
|
const controlButtons = CommandBarComponentButtonFactory.createControlCommandBarButtons(container);
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import { AuthType } from "../../../AuthType";
|
import { AuthType } from "../../../AuthType";
|
||||||
import { DatabaseAccount } from "../../../Contracts/DataModels";
|
import { DatabaseAccount } from "../../../Contracts/DataModels";
|
||||||
|
import { CollectionBase } from "../../../Contracts/ViewModels";
|
||||||
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
||||||
import { updateUserContext } from "../../../UserContext";
|
import { updateUserContext } from "../../../UserContext";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import NotebookManager from "../../Notebook/NotebookManager";
|
import NotebookManager from "../../Notebook/NotebookManager";
|
||||||
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
||||||
|
|
||||||
describe("CommandBarComponentButtonFactory tests", () => {
|
describe("CommandBarComponentButtonFactory tests", () => {
|
||||||
let mockExplorer: Explorer;
|
let mockExplorer: Explorer;
|
||||||
|
|
||||||
|
afterEach(() => useSelectedNode.getState().setSelectedNode(undefined));
|
||||||
|
|
||||||
describe("Enable Azure Synapse Link Button", () => {
|
describe("Enable Azure Synapse Link Button", () => {
|
||||||
const enableAzureSynapseLinkBtnLabel = "Enable Azure Synapse Link";
|
const enableAzureSynapseLinkBtnLabel = "Enable Azure Synapse Link";
|
||||||
|
const selectedNodeState = useSelectedNode.getState();
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
mockExplorer = {} as Explorer;
|
mockExplorer = {} as Explorer;
|
||||||
@@ -23,17 +28,12 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
});
|
});
|
||||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||||
|
|
||||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
|
||||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||||
mockExplorer.isRunningOnNationalCloud = () => false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Account is not serverless - button should be visible", () => {
|
it("Account is not serverless - button should be visible", () => {
|
||||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
|
||||||
const enableAzureSynapseLinkBtn = buttons.find(
|
const enableAzureSynapseLinkBtn = buttons.find(
|
||||||
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
|
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
|
||||||
);
|
);
|
||||||
@@ -41,9 +41,14 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Account is serverless - button should be hidden", () => {
|
it("Account is serverless - button should be hidden", () => {
|
||||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => true);
|
updateUserContext({
|
||||||
|
databaseAccount: {
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
properties: {
|
||||||
|
capabilities: [{ name: "EnableServerless" }],
|
||||||
|
},
|
||||||
|
} as DatabaseAccount,
|
||||||
|
});
|
||||||
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const enableAzureSynapseLinkBtn = buttons.find(
|
const enableAzureSynapseLinkBtn = buttons.find(
|
||||||
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
|
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
|
||||||
);
|
);
|
||||||
@@ -53,10 +58,12 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
|
|
||||||
describe("Enable notebook button", () => {
|
describe("Enable notebook button", () => {
|
||||||
const enableNotebookBtnLabel = "Enable Notebooks (Preview)";
|
const enableNotebookBtnLabel = "Enable Notebooks (Preview)";
|
||||||
|
const selectedNodeState = useSelectedNode.getState();
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
mockExplorer = {} as Explorer;
|
mockExplorer = {} as Explorer;
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
|
portalEnv: "prod",
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
properties: {
|
properties: {
|
||||||
capabilities: [{ name: "EnableTable" }],
|
capabilities: [{ name: "EnableTable" }],
|
||||||
@@ -64,18 +71,19 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
});
|
});
|
||||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
});
|
||||||
|
|
||||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
afterEach(() => {
|
||||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
updateUserContext({
|
||||||
|
portalEnv: "prod",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Notebooks is already enabled - button should be hidden", () => {
|
it("Notebooks is already enabled - button should be hidden", () => {
|
||||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||||
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
|
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
||||||
expect(enableNotebookBtn).toBeUndefined();
|
expect(enableNotebookBtn).toBeUndefined();
|
||||||
});
|
});
|
||||||
@@ -83,9 +91,11 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
it("Account is running on one of the national clouds - button should be hidden", () => {
|
it("Account is running on one of the national clouds - button should be hidden", () => {
|
||||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||||
mockExplorer.isRunningOnNationalCloud = ko.observable(true);
|
updateUserContext({
|
||||||
|
portalEnv: "mooncake",
|
||||||
|
});
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
||||||
expect(enableNotebookBtn).toBeUndefined();
|
expect(enableNotebookBtn).toBeUndefined();
|
||||||
});
|
});
|
||||||
@@ -93,9 +103,8 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
it("Notebooks is not enabled but is available - button should be shown and enabled", () => {
|
it("Notebooks is not enabled but is available - button should be shown and enabled", () => {
|
||||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||||
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
|
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
||||||
expect(enableNotebookBtn).toBeDefined();
|
expect(enableNotebookBtn).toBeDefined();
|
||||||
expect(enableNotebookBtn.disabled).toBe(false);
|
expect(enableNotebookBtn.disabled).toBe(false);
|
||||||
@@ -105,9 +114,8 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
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 shown and disabled", () => {
|
||||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||||
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
|
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
||||||
expect(enableNotebookBtn).toBeDefined();
|
expect(enableNotebookBtn).toBeDefined();
|
||||||
expect(enableNotebookBtn.disabled).toBe(true);
|
expect(enableNotebookBtn.disabled).toBe(true);
|
||||||
@@ -119,6 +127,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
|
|
||||||
describe("Open Mongo Shell button", () => {
|
describe("Open Mongo Shell button", () => {
|
||||||
const openMongoShellBtnLabel = "Open Mongo Shell";
|
const openMongoShellBtnLabel = "Open Mongo Shell";
|
||||||
|
const selectedNodeState = useSelectedNode.getState();
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
mockExplorer = {} as Explorer;
|
mockExplorer = {} as Explorer;
|
||||||
@@ -130,9 +139,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
});
|
});
|
||||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||||
|
|
||||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
|
||||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
|
||||||
mockExplorer.isShellEnabled = ko.observable(true);
|
mockExplorer.isShellEnabled = ko.observable(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -148,7 +154,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||||
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
|
|
||||||
mockExplorer.isShellEnabled = ko.observable(true);
|
mockExplorer.isShellEnabled = ko.observable(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -156,21 +162,23 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
updateUserContext({
|
updateUserContext({
|
||||||
apiType: "SQL",
|
apiType: "SQL",
|
||||||
});
|
});
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||||
expect(openMongoShellBtn).toBeUndefined();
|
expect(openMongoShellBtn).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Running on a national cloud - button should be hidden", () => {
|
it("Running on a national cloud - button should be hidden", () => {
|
||||||
mockExplorer.isRunningOnNationalCloud = ko.observable(true);
|
updateUserContext({
|
||||||
|
portalEnv: "mooncake",
|
||||||
|
});
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||||
expect(openMongoShellBtn).toBeUndefined();
|
expect(openMongoShellBtn).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Notebooks is not enabled and is unavailable - button should be hidden", () => {
|
it("Notebooks is not enabled and is unavailable - button should be hidden", () => {
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||||
expect(openMongoShellBtn).toBeUndefined();
|
expect(openMongoShellBtn).toBeUndefined();
|
||||||
});
|
});
|
||||||
@@ -178,7 +186,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
it("Notebooks is not enabled and is available - button should be hidden", () => {
|
it("Notebooks is not enabled and is available - button should be hidden", () => {
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||||
expect(openMongoShellBtn).toBeUndefined();
|
expect(openMongoShellBtn).toBeUndefined();
|
||||||
});
|
});
|
||||||
@@ -186,7 +194,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
||||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||||
expect(openMongoShellBtn).toBeDefined();
|
expect(openMongoShellBtn).toBeDefined();
|
||||||
expect(openMongoShellBtn.disabled).toBe(false);
|
expect(openMongoShellBtn.disabled).toBe(false);
|
||||||
@@ -197,7 +205,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||||
expect(openMongoShellBtn).toBeDefined();
|
expect(openMongoShellBtn).toBeDefined();
|
||||||
expect(openMongoShellBtn.disabled).toBe(false);
|
expect(openMongoShellBtn.disabled).toBe(false);
|
||||||
@@ -209,7 +217,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||||
mockExplorer.isShellEnabled = ko.observable(false);
|
mockExplorer.isShellEnabled = ko.observable(false);
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||||
expect(openMongoShellBtn).toBeUndefined();
|
expect(openMongoShellBtn).toBeUndefined();
|
||||||
});
|
});
|
||||||
@@ -217,6 +225,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
|
|
||||||
describe("Open Cassandra Shell button", () => {
|
describe("Open Cassandra Shell button", () => {
|
||||||
const openCassandraShellBtnLabel = "Open Cassandra Shell";
|
const openCassandraShellBtnLabel = "Open Cassandra Shell";
|
||||||
|
const selectedNodeState = useSelectedNode.getState();
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
mockExplorer = {} as Explorer;
|
mockExplorer = {} as Explorer;
|
||||||
@@ -228,9 +237,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
});
|
});
|
||||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||||
|
|
||||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
|
||||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -243,7 +249,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||||
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Cassandra Api not available - button should be hidden", () => {
|
it("Cassandra Api not available - button should be hidden", () => {
|
||||||
@@ -255,21 +260,23 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
});
|
});
|
||||||
console.log(mockExplorer);
|
console.log(mockExplorer);
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||||
expect(openCassandraShellBtn).toBeUndefined();
|
expect(openCassandraShellBtn).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Running on a national cloud - button should be hidden", () => {
|
it("Running on a national cloud - button should be hidden", () => {
|
||||||
mockExplorer.isRunningOnNationalCloud = ko.observable(true);
|
updateUserContext({
|
||||||
|
portalEnv: "mooncake",
|
||||||
|
});
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||||
expect(openCassandraShellBtn).toBeUndefined();
|
expect(openCassandraShellBtn).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 shown and disabled", () => {
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||||
expect(openCassandraShellBtn).toBeUndefined();
|
expect(openCassandraShellBtn).toBeUndefined();
|
||||||
});
|
});
|
||||||
@@ -277,7 +284,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
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 shown and enabled", () => {
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||||
expect(openCassandraShellBtn).toBeUndefined();
|
expect(openCassandraShellBtn).toBeUndefined();
|
||||||
});
|
});
|
||||||
@@ -285,7 +292,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
||||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||||
expect(openCassandraShellBtn).toBeDefined();
|
expect(openCassandraShellBtn).toBeDefined();
|
||||||
expect(openCassandraShellBtn.disabled).toBe(false);
|
expect(openCassandraShellBtn.disabled).toBe(false);
|
||||||
@@ -296,7 +303,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||||
expect(openCassandraShellBtn).toBeDefined();
|
expect(openCassandraShellBtn).toBeDefined();
|
||||||
expect(openCassandraShellBtn.disabled).toBe(false);
|
expect(openCassandraShellBtn.disabled).toBe(false);
|
||||||
@@ -307,6 +314,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
describe("GitHub buttons", () => {
|
describe("GitHub buttons", () => {
|
||||||
const connectToGitHubBtnLabel = "Connect to GitHub";
|
const connectToGitHubBtnLabel = "Connect to GitHub";
|
||||||
const manageGitHubSettingsBtnLabel = "Manage GitHub settings";
|
const manageGitHubSettingsBtnLabel = "Manage GitHub settings";
|
||||||
|
const selectedNodeState = useSelectedNode.getState();
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
mockExplorer = {} as Explorer;
|
mockExplorer = {} as Explorer;
|
||||||
@@ -319,12 +327,10 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||||
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
|
|
||||||
mockExplorer.notebookManager = new NotebookManager();
|
mockExplorer.notebookManager = new NotebookManager();
|
||||||
mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined);
|
mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined);
|
||||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -338,7 +344,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
it("Notebooks is enabled and GitHubOAuthService is not logged in - connect to github button should be visible", () => {
|
it("Notebooks is enabled and GitHubOAuthService is not logged in - connect to github button should be visible", () => {
|
||||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel);
|
const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel);
|
||||||
expect(connectToGitHubBtn).toBeDefined();
|
expect(connectToGitHubBtn).toBeDefined();
|
||||||
});
|
});
|
||||||
@@ -347,7 +353,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||||
mockExplorer.notebookManager.gitHubOAuthService.isLoggedIn = jest.fn().mockReturnValue(true);
|
mockExplorer.notebookManager.gitHubOAuthService.isLoggedIn = jest.fn().mockReturnValue(true);
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const manageGitHubSettingsBtn = buttons.find(
|
const manageGitHubSettingsBtn = buttons.find(
|
||||||
(button) => button.commandButtonLabel === manageGitHubSettingsBtnLabel
|
(button) => button.commandButtonLabel === manageGitHubSettingsBtnLabel
|
||||||
);
|
);
|
||||||
@@ -355,7 +361,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Notebooks is not enabled - connect to github and manage github settings buttons should be hidden", () => {
|
it("Notebooks is not enabled - connect to github and manage github settings buttons should be hidden", () => {
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
|
|
||||||
const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel);
|
const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel);
|
||||||
expect(connectToGitHubBtn).toBeUndefined();
|
expect(connectToGitHubBtn).toBeUndefined();
|
||||||
@@ -368,11 +374,13 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("Resource token", () => {
|
describe("Resource token", () => {
|
||||||
|
const mockCollection = { id: ko.observable("test") } as CollectionBase;
|
||||||
|
useSelectedNode.getState().setSelectedNode(mockCollection);
|
||||||
|
const selectedNodeState = useSelectedNode.getState();
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
mockExplorer = {} as Explorer;
|
mockExplorer = {} as Explorer;
|
||||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
mockExplorer.resourceTokenCollection = ko.observable(mockCollection);
|
||||||
mockExplorer.isResourceTokenCollectionNodeSelected = ko.computed(() => true);
|
|
||||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
authType: AuthType.ResourceToken,
|
authType: AuthType.ResourceToken,
|
||||||
});
|
});
|
||||||
@@ -384,7 +392,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
kind: "DocumentDB",
|
kind: "DocumentDB",
|
||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
});
|
});
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
expect(buttons.length).toBe(2);
|
expect(buttons.length).toBe(2);
|
||||||
expect(buttons[0].commandButtonLabel).toBe("New SQL Query");
|
expect(buttons[0].commandButtonLabel).toBe("New SQL Query");
|
||||||
expect(buttons[0].disabled).toBe(false);
|
expect(buttons[0].disabled).toBe(false);
|
||||||
|
|||||||
@@ -24,16 +24,23 @@ import * as ViewModels from "../../../Contracts/ViewModels";
|
|||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils";
|
import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils";
|
||||||
|
import { isServerlessAccount } from "../../../Utils/CapabilityUtils";
|
||||||
|
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { OpenFullScreen } from "../../OpenFullScreen";
|
import { OpenFullScreen } from "../../OpenFullScreen";
|
||||||
import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane";
|
import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane";
|
||||||
|
import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane";
|
||||||
|
import { SelectedNodeState } from "../../useSelectedNode";
|
||||||
|
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
|
|
||||||
export function createStaticCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
|
export function createStaticCommandBarButtons(
|
||||||
|
container: Explorer,
|
||||||
|
selectedNodeState: SelectedNodeState
|
||||||
|
): CommandButtonComponentProps[] {
|
||||||
if (userContext.authType === AuthType.ResourceToken) {
|
if (userContext.authType === AuthType.ResourceToken) {
|
||||||
return createStaticCommandBarButtonsForResourceToken(container);
|
return createStaticCommandBarButtonsForResourceToken(container, selectedNodeState);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newCollectionBtn = createNewCollectionGroup(container);
|
const newCollectionBtn = createNewCollectionGroup(container);
|
||||||
@@ -68,7 +75,9 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
|
|||||||
|
|
||||||
buttons.push(createNotebookWorkspaceResetButton(container));
|
buttons.push(createNotebookWorkspaceResetButton(container));
|
||||||
if (
|
if (
|
||||||
(userContext.apiType === "Mongo" && container.isShellEnabled() && container.isDatabaseNodeOrNoneSelected()) ||
|
(userContext.apiType === "Mongo" &&
|
||||||
|
container.isShellEnabled() &&
|
||||||
|
selectedNodeState.isDatabaseNodeOrNoneSelected()) ||
|
||||||
userContext.apiType === "Cassandra"
|
userContext.apiType === "Cassandra"
|
||||||
) {
|
) {
|
||||||
buttons.push(createDivider());
|
buttons.push(createDivider());
|
||||||
@@ -79,23 +88,23 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!container.isRunningOnNationalCloud()) {
|
if (!isRunningOnNationalCloud()) {
|
||||||
buttons.push(createEnableNotebooksButton(container));
|
buttons.push(createEnableNotebooksButton(container));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!container.isDatabaseNodeOrNoneSelected()) {
|
if (!selectedNodeState.isDatabaseNodeOrNoneSelected()) {
|
||||||
const isQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
|
const isQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
|
||||||
|
|
||||||
if (isQuerySupported) {
|
if (isQuerySupported) {
|
||||||
buttons.push(createDivider());
|
buttons.push(createDivider());
|
||||||
const newSqlQueryBtn = createNewSQLQueryButton(container);
|
const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState);
|
||||||
buttons.push(newSqlQueryBtn);
|
buttons.push(newSqlQueryBtn);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isQuerySupported && container.selectedNode() && container.findSelectedCollection()) {
|
if (isQuerySupported && selectedNodeState.findSelectedCollection()) {
|
||||||
const openQueryBtn = createOpenQueryButton(container);
|
const openQueryBtn = createOpenQueryButton(container);
|
||||||
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton(container)];
|
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton()];
|
||||||
buttons.push(openQueryBtn);
|
buttons.push(openQueryBtn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,16 +114,16 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
|
|||||||
iconSrc: AddStoredProcedureIcon,
|
iconSrc: AddStoredProcedureIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
|
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
|
||||||
},
|
},
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
hasPopup: true,
|
hasPopup: true,
|
||||||
disabled: container.isDatabaseNodeOrNoneSelected(),
|
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
|
||||||
};
|
};
|
||||||
|
|
||||||
newStoredProcedureBtn.children = createScriptCommandButtons(container);
|
newStoredProcedureBtn.children = createScriptCommandButtons(selectedNodeState);
|
||||||
buttons.push(newStoredProcedureBtn);
|
buttons.push(newStoredProcedureBtn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,16 +131,19 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
|
|||||||
return buttons;
|
return buttons;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createContextCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
|
export function createContextCommandBarButtons(
|
||||||
|
container: Explorer,
|
||||||
|
selectedNodeState: SelectedNodeState
|
||||||
|
): CommandButtonComponentProps[] {
|
||||||
const buttons: CommandButtonComponentProps[] = [];
|
const buttons: CommandButtonComponentProps[] = [];
|
||||||
|
|
||||||
if (!container.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") {
|
if (!selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") {
|
||||||
const label = container.isShellEnabled() ? "Open Mongo Shell" : "New Shell";
|
const label = container.isShellEnabled() ? "Open Mongo Shell" : "New Shell";
|
||||||
const newMongoShellBtn: CommandButtonComponentProps = {
|
const newMongoShellBtn: CommandButtonComponentProps = {
|
||||||
iconSrc: HostedTerminalIcon,
|
iconSrc: HostedTerminalIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||||
if (container.isShellEnabled()) {
|
if (container.isShellEnabled()) {
|
||||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||||
} else {
|
} else {
|
||||||
@@ -141,7 +153,7 @@ export function createContextCommandBarButtons(container: Explorer): CommandButt
|
|||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
hasPopup: true,
|
hasPopup: true,
|
||||||
disabled: container.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo",
|
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo",
|
||||||
};
|
};
|
||||||
buttons.push(newMongoShellBtn);
|
buttons.push(newMongoShellBtn);
|
||||||
}
|
}
|
||||||
@@ -154,7 +166,7 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
|
|||||||
{
|
{
|
||||||
iconSrc: SettingsIcon,
|
iconSrc: SettingsIcon,
|
||||||
iconAlt: "Settings",
|
iconAlt: "Settings",
|
||||||
onCommandClick: container.openSettingPane,
|
onCommandClick: () => useSidePanel.getState().openSidePanel("Settings", <SettingsPane />),
|
||||||
commandButtonLabel: undefined,
|
commandButtonLabel: undefined,
|
||||||
ariaLabel: "Settings",
|
ariaLabel: "Settings",
|
||||||
tooltipText: "Settings",
|
tooltipText: "Settings",
|
||||||
@@ -163,7 +175,10 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (container.isHostedDataExplorerEnabled()) {
|
const showOpenFullScreen =
|
||||||
|
configContext.platform === Platform.Portal && !isRunningOnNationalCloud() && userContext.apiType !== "Gremlin";
|
||||||
|
|
||||||
|
if (showOpenFullScreen) {
|
||||||
const label = "Open Full Screen";
|
const label = "Open Full Screen";
|
||||||
const fullScreenButton: CommandButtonComponentProps = {
|
const fullScreenButton: CommandButtonComponentProps = {
|
||||||
iconSrc: OpenInTabIcon,
|
iconSrc: OpenInTabIcon,
|
||||||
@@ -175,7 +190,7 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
|
|||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
tooltipText: label,
|
tooltipText: label,
|
||||||
hasPopup: false,
|
hasPopup: false,
|
||||||
disabled: !container.isHostedDataExplorerEnabled(),
|
disabled: !showOpenFullScreen,
|
||||||
className: "OpenFullScreen",
|
className: "OpenFullScreen",
|
||||||
};
|
};
|
||||||
buttons.push(fullScreenButton);
|
buttons.push(fullScreenButton);
|
||||||
@@ -234,7 +249,7 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container.isServerlessEnabled()) {
|
if (isServerlessAccount()) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,20 +288,20 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNewSQLQueryButton(container: Explorer): CommandButtonComponentProps {
|
function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandButtonComponentProps {
|
||||||
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
|
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
|
||||||
const label = "New SQL Query";
|
const label = "New SQL Query";
|
||||||
return {
|
return {
|
||||||
iconSrc: AddSqlQueryIcon,
|
iconSrc: AddSqlQueryIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection);
|
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection);
|
||||||
},
|
},
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
hasPopup: true,
|
hasPopup: true,
|
||||||
disabled: container.isDatabaseNodeOrNoneSelected(),
|
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
|
||||||
};
|
};
|
||||||
} else if (userContext.apiType === "Mongo") {
|
} else if (userContext.apiType === "Mongo") {
|
||||||
const label = "New Query";
|
const label = "New Query";
|
||||||
@@ -294,23 +309,24 @@ function createNewSQLQueryButton(container: Explorer): CommandButtonComponentPro
|
|||||||
iconSrc: AddSqlQueryIcon,
|
iconSrc: AddSqlQueryIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection);
|
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection);
|
||||||
},
|
},
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
hasPopup: true,
|
hasPopup: true,
|
||||||
disabled: container.isDatabaseNodeOrNoneSelected(),
|
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createScriptCommandButtons(container: Explorer): CommandButtonComponentProps[] {
|
export function createScriptCommandButtons(selectedNodeState: SelectedNodeState): CommandButtonComponentProps[] {
|
||||||
const buttons: CommandButtonComponentProps[] = [];
|
const buttons: CommandButtonComponentProps[] = [];
|
||||||
|
|
||||||
const shouldEnableScriptsCommands: boolean = !container.isDatabaseNodeOrNoneSelected() && areScriptsSupported();
|
const shouldEnableScriptsCommands: boolean =
|
||||||
|
!selectedNodeState.isDatabaseNodeOrNoneSelected() && areScriptsSupported();
|
||||||
|
|
||||||
if (shouldEnableScriptsCommands) {
|
if (shouldEnableScriptsCommands) {
|
||||||
const label = "New Stored Procedure";
|
const label = "New Stored Procedure";
|
||||||
@@ -318,13 +334,13 @@ export function createScriptCommandButtons(container: Explorer): CommandButtonCo
|
|||||||
iconSrc: AddStoredProcedureIcon,
|
iconSrc: AddStoredProcedureIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
|
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
|
||||||
},
|
},
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
hasPopup: true,
|
hasPopup: true,
|
||||||
disabled: container.isDatabaseNodeOrNoneSelected(),
|
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
|
||||||
};
|
};
|
||||||
buttons.push(newStoredProcedureBtn);
|
buttons.push(newStoredProcedureBtn);
|
||||||
}
|
}
|
||||||
@@ -335,13 +351,13 @@ export function createScriptCommandButtons(container: Explorer): CommandButtonCo
|
|||||||
iconSrc: AddUdfIcon,
|
iconSrc: AddUdfIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection);
|
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection);
|
||||||
},
|
},
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
hasPopup: true,
|
hasPopup: true,
|
||||||
disabled: container.isDatabaseNodeOrNoneSelected(),
|
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
|
||||||
};
|
};
|
||||||
buttons.push(newUserDefinedFunctionBtn);
|
buttons.push(newUserDefinedFunctionBtn);
|
||||||
}
|
}
|
||||||
@@ -352,13 +368,13 @@ export function createScriptCommandButtons(container: Explorer): CommandButtonCo
|
|||||||
iconSrc: AddTriggerIcon,
|
iconSrc: AddTriggerIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection);
|
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection);
|
||||||
},
|
},
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
hasPopup: true,
|
hasPopup: true,
|
||||||
disabled: container.isDatabaseNodeOrNoneSelected(),
|
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
|
||||||
};
|
};
|
||||||
buttons.push(newTriggerBtn);
|
buttons.push(newTriggerBtn);
|
||||||
}
|
}
|
||||||
@@ -405,12 +421,12 @@ function createOpenQueryButton(container: Explorer): CommandButtonComponentProps
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createOpenQueryFromDiskButton(container: Explorer): CommandButtonComponentProps {
|
function createOpenQueryFromDiskButton(): CommandButtonComponentProps {
|
||||||
const label = "Open Query From Disk";
|
const label = "Open Query From Disk";
|
||||||
return {
|
return {
|
||||||
iconSrc: OpenQueryFromDiskIcon,
|
iconSrc: OpenQueryFromDiskIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: () => useSidePanel.getState().openSidePanel("Load Query", <LoadQueryPane explorer={container} />),
|
onCommandClick: () => useSidePanel.getState().openSidePanel("Load Query", <LoadQueryPane />),
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
hasPopup: true,
|
hasPopup: true,
|
||||||
@@ -531,19 +547,25 @@ function createManageGitHubAccountButton(container: Explorer): CommandButtonComp
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createStaticCommandBarButtonsForResourceToken(container: Explorer): CommandButtonComponentProps[] {
|
function createStaticCommandBarButtonsForResourceToken(
|
||||||
const newSqlQueryBtn = createNewSQLQueryButton(container);
|
container: Explorer,
|
||||||
|
selectedNodeState: SelectedNodeState
|
||||||
|
): CommandButtonComponentProps[] {
|
||||||
|
const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState);
|
||||||
const openQueryBtn = createOpenQueryButton(container);
|
const openQueryBtn = createOpenQueryButton(container);
|
||||||
|
|
||||||
newSqlQueryBtn.disabled = !container.isResourceTokenCollectionNodeSelected();
|
const isResourceTokenCollectionNodeSelected: boolean =
|
||||||
|
container.resourceTokenCollection() &&
|
||||||
|
container.resourceTokenCollection().id() === selectedNodeState.selectedNode?.id();
|
||||||
|
newSqlQueryBtn.disabled = !isResourceTokenCollectionNodeSelected;
|
||||||
newSqlQueryBtn.onCommandClick = () => {
|
newSqlQueryBtn.onCommandClick = () => {
|
||||||
const resourceTokenCollection: ViewModels.CollectionBase = container.resourceTokenCollection();
|
const resourceTokenCollection: ViewModels.CollectionBase = container.resourceTokenCollection();
|
||||||
resourceTokenCollection && resourceTokenCollection.onNewQueryClick(resourceTokenCollection, undefined);
|
resourceTokenCollection && resourceTokenCollection.onNewQueryClick(resourceTokenCollection, undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
openQueryBtn.disabled = !container.isResourceTokenCollectionNodeSelected();
|
openQueryBtn.disabled = !isResourceTokenCollectionNodeSelected;
|
||||||
if (!openQueryBtn.disabled) {
|
if (!openQueryBtn.disabled) {
|
||||||
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton(container)];
|
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton()];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [newSqlQueryBtn, openQueryBtn];
|
return [newSqlQueryBtn, openQueryBtn];
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
/**
|
|
||||||
* React component for control bar
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import {
|
|
||||||
CommandButtonComponent,
|
|
||||||
CommandButtonComponentProps,
|
|
||||||
} from "../../Controls/CommandButton/CommandButtonComponent";
|
|
||||||
|
|
||||||
export interface ControlBarComponentProps {
|
|
||||||
buttons: CommandButtonComponentProps[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ControlBarComponent extends React.Component<ControlBarComponentProps> {
|
|
||||||
private static renderButtons(commandButtonOptions: CommandButtonComponentProps[]): JSX.Element[] {
|
|
||||||
return commandButtonOptions.map(
|
|
||||||
(btn: CommandButtonComponentProps, index: number): JSX.Element => {
|
|
||||||
// Remove label
|
|
||||||
btn.commandButtonLabel = undefined;
|
|
||||||
return CommandButtonComponent.renderButton(btn, `${index}`);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(): JSX.Element {
|
|
||||||
if (!this.props.buttons || this.props.buttons.length < 1) {
|
|
||||||
return <React.Fragment />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <React.Fragment>{ControlBarComponent.renderButtons(this.props.buttons)}</React.Fragment>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
/**
|
|
||||||
* This adapter is responsible to render the React component
|
|
||||||
* If the component signals a change through the callback passed in the properties, it must render the React component when appropriate
|
|
||||||
* and update any knockout observables passed from the parent.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as ko from "knockout";
|
|
||||||
import * as React from "react";
|
|
||||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
|
||||||
import { ControlBarComponent } from "./ControlBarComponent";
|
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
|
||||||
|
|
||||||
export class ControlBarComponentAdapter implements ReactAdapter {
|
|
||||||
public parameters: ko.Observable<number>;
|
|
||||||
|
|
||||||
constructor(private buttons: ko.ObservableArray<CommandButtonComponentProps>) {
|
|
||||||
this.buttons.subscribe(() => this.forceRender());
|
|
||||||
this.parameters = ko.observable<number>(Date.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
|
||||||
return <ControlBarComponent buttons={this.buttons()} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
public forceRender(): void {
|
|
||||||
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Interface for the data/content that will be recorded
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ConsoleData {
|
||||||
|
type: ConsoleDataType;
|
||||||
|
date: string;
|
||||||
|
message: string;
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ConsoleDataType {
|
||||||
|
Info = 0,
|
||||||
|
Error = 1,
|
||||||
|
InProgress = 2,
|
||||||
|
}
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import { ConsoleDataType } from "./ConsoleData";
|
||||||
ConsoleDataType,
|
import { NotificationConsoleComponent, NotificationConsoleComponentProps } from "./NotificationConsoleComponent";
|
||||||
NotificationConsoleComponent,
|
|
||||||
NotificationConsoleComponentProps,
|
|
||||||
} from "./NotificationConsoleComponent";
|
|
||||||
|
|
||||||
describe("NotificationConsoleComponent", () => {
|
describe("NotificationConsoleComponent", () => {
|
||||||
const createBlankProps = (): NotificationConsoleComponentProps => {
|
const createBlankProps = (): NotificationConsoleComponentProps => {
|
||||||
|
|||||||
@@ -17,25 +17,7 @@ import ChevronUpIcon from "../../../../images/QueryBuilder/CollapseChevronUp_16x
|
|||||||
import { ClientDefaults, KeyCodes } from "../../../Common/Constants";
|
import { ClientDefaults, KeyCodes } from "../../../Common/Constants";
|
||||||
import { useNotificationConsole } from "../../../hooks/useNotificationConsole";
|
import { useNotificationConsole } from "../../../hooks/useNotificationConsole";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
|
import { ConsoleData, ConsoleDataType } from "./ConsoleData";
|
||||||
/**
|
|
||||||
* Log levels
|
|
||||||
*/
|
|
||||||
export enum ConsoleDataType {
|
|
||||||
Info = 0,
|
|
||||||
Error = 1,
|
|
||||||
InProgress = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for the data/content that will be recorded
|
|
||||||
*/
|
|
||||||
export interface ConsoleData {
|
|
||||||
type: ConsoleDataType;
|
|
||||||
date: string;
|
|
||||||
message: string;
|
|
||||||
id?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NotificationConsoleComponentProps {
|
export interface NotificationConsoleComponentProps {
|
||||||
isConsoleExpanded: boolean;
|
isConsoleExpanded: boolean;
|
||||||
@@ -323,14 +305,13 @@ const PrPreview = (props: { pr: string }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NotificationConsole: React.FC<
|
export const NotificationConsole: React.FC = () => {
|
||||||
Pick<NotificationConsoleComponentProps, "consoleData" | "inProgressConsoleDataIdToBeDeleted">
|
|
||||||
> = ({
|
|
||||||
consoleData,
|
|
||||||
inProgressConsoleDataIdToBeDeleted,
|
|
||||||
}: Pick<NotificationConsoleComponentProps, "consoleData" | "inProgressConsoleDataIdToBeDeleted">) => {
|
|
||||||
const setIsExpanded = useNotificationConsole((state) => state.setIsExpanded);
|
const setIsExpanded = useNotificationConsole((state) => state.setIsExpanded);
|
||||||
const isExpanded = useNotificationConsole((state) => state.isExpanded);
|
const isExpanded = useNotificationConsole((state) => state.isExpanded);
|
||||||
|
const consoleData = useNotificationConsole((state) => state.consoleData);
|
||||||
|
const inProgressConsoleDataIdToBeDeleted = useNotificationConsole(
|
||||||
|
(state) => state.inProgressConsoleDataIdToBeDeleted
|
||||||
|
);
|
||||||
// TODO Refactor NotificationConsoleComponent into a functional component and remove this wrapper
|
// TODO Refactor NotificationConsoleComponent into a functional component and remove this wrapper
|
||||||
// This component only exists so we can use hooks and pass them down to a non-functional component
|
// This component only exists so we can use hooks and pass them down to a non-functional component
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
makeStateRecord,
|
makeStateRecord,
|
||||||
makeTransformsRecord,
|
makeTransformsRecord,
|
||||||
} from "@nteract/core";
|
} from "@nteract/core";
|
||||||
import { configOption, createConfigCollection, defineConfigOption } from "@nteract/mythic-configuration";
|
import { configOption, defineConfigOption } from "@nteract/mythic-configuration";
|
||||||
import { Media } from "@nteract/outputs";
|
import { Media } from "@nteract/outputs";
|
||||||
import TransformVDOM from "@nteract/transform-vdom";
|
import TransformVDOM from "@nteract/transform-vdom";
|
||||||
import * as Immutable from "immutable";
|
import * as Immutable from "immutable";
|
||||||
@@ -242,22 +242,27 @@ export class NotebookClientV2 {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Additional configuration
|
// Additional configuration
|
||||||
this.store.dispatch(configOption("editorType").action(params.cellEditorType ?? "monaco"));
|
this.store.dispatch(configOption("editorType").action(params.cellEditorType ?? "codemirror"));
|
||||||
this.store.dispatch(
|
this.store.dispatch(
|
||||||
configOption("autoSaveInterval").action(params.autoSaveInterval ?? Constants.Notebook.autoSaveIntervalMs)
|
configOption("autoSaveInterval").action(params.autoSaveInterval ?? Constants.Notebook.autoSaveIntervalMs)
|
||||||
);
|
);
|
||||||
createConfigCollection({
|
this.store.dispatch(configOption("codeMirror.lineNumbers").action(true));
|
||||||
key: "monaco",
|
|
||||||
});
|
const readOnlyConfigOption = configOption("codeMirror.readOnly");
|
||||||
|
const readOnlyValue = params.isReadOnly ? "nocursor" : undefined;
|
||||||
|
if (!readOnlyConfigOption) {
|
||||||
defineConfigOption({
|
defineConfigOption({
|
||||||
label: "Show Line numbers",
|
label: "Read-only",
|
||||||
key: "monaco.lineNumbers",
|
key: "codeMirror.readOnly",
|
||||||
values: [
|
values: [
|
||||||
{ label: "Yes", value: true },
|
{ label: "Read-Only", value: "nocursor" },
|
||||||
{ label: "No", value: false },
|
{ label: "Not read-only", value: undefined },
|
||||||
],
|
],
|
||||||
defaultValue: true,
|
defaultValue: readOnlyValue,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
this.store.dispatch(readOnlyConfigOption.action(readOnlyValue));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
|||||||
import * as Logger from "../../Common/Logger";
|
import * as Logger from "../../Common/Logger";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
import { createOrUpdate, destroy } from "../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
||||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
|
||||||
export class NotebookContainerClient {
|
export class NotebookContainerClient {
|
||||||
@@ -130,16 +131,18 @@ export class NotebookContainerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async recreateNotebookWorkspaceAsync(): Promise<void> {
|
private async recreateNotebookWorkspaceAsync(): Promise<void> {
|
||||||
const explorer = window.dataExplorer;
|
|
||||||
const { databaseAccount } = userContext;
|
const { databaseAccount } = userContext;
|
||||||
if (!databaseAccount?.id) {
|
if (!databaseAccount?.id) {
|
||||||
throw new Error("DataExplorer not initialized");
|
throw new Error("DataExplorer not initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
const notebookWorkspaceManager = explorer.notebookWorkspaceManager;
|
|
||||||
try {
|
try {
|
||||||
await notebookWorkspaceManager.deleteNotebookWorkspaceAsync(databaseAccount?.id, "default");
|
await destroy(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, "default");
|
||||||
await notebookWorkspaceManager.createNotebookWorkspaceAsync(databaseAccount?.id, "default");
|
await createOrUpdate(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
"default"
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.logError(getErrorMessage(error), "NotebookContainerClient/recreateNotebookWorkspaceAsync");
|
Logger.logError(getErrorMessage(error), "NotebookContainerClient/recreateNotebookWorkspaceAsync");
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
|||||||
@@ -10,18 +10,30 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror-scroll {
|
.CodeMirror-scroll {
|
||||||
background-color: #f5f5f5;
|
overflow: hidden !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror-lines {
|
.CodeMirror-lines {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.CodeMirror {
|
||||||
|
height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-scroll,
|
||||||
|
.CodeMirror-linenumber,
|
||||||
|
.CodeMirror-gutters {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
.nteract-cell:hover {
|
.nteract-cell:hover {
|
||||||
border: 1px solid #0078d4;
|
border: 1px solid #0078d4;
|
||||||
border-left: 3px solid #0078d4;
|
border-left: 3px solid #0078d4;
|
||||||
|
|
||||||
.CodeMirror-scroll {
|
.CodeMirror-scroll,
|
||||||
|
.CodeMirror-linenumber,
|
||||||
|
.CodeMirror-gutters {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { actions, ContentRef } from "@nteract/core";
|
import { actions, ContentRef } from "@nteract/core";
|
||||||
import { Cells, CodeCell, RawCell } from "@nteract/stateful-components";
|
import { Cells, CodeCell, RawCell } from "@nteract/stateful-components";
|
||||||
import MonacoEditor from "@nteract/stateful-components/lib/inputs/connected-editors/monacoEditor";
|
import CodeMirrorEditor from "@nteract/stateful-components/lib/inputs/connected-editors/codemirror";
|
||||||
import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor";
|
import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor";
|
||||||
import Prompt, { PassedPromptProps } from "@nteract/stateful-components/lib/inputs/prompt";
|
import Prompt, { PassedPromptProps } from "@nteract/stateful-components/lib/inputs/prompt";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
@@ -67,8 +67,8 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
|||||||
? () => <SandboxOutputs id={id} contentRef={contentRef} />
|
? () => <SandboxOutputs id={id} contentRef={contentRef} />
|
||||||
: undefined,
|
: undefined,
|
||||||
editor: {
|
editor: {
|
||||||
monaco: (props: PassedEditorProps) =>
|
codemirror: (props: PassedEditorProps) =>
|
||||||
this.props.hideInputs ? <></> : <MonacoEditor readOnly={true} {...props} editorType={"monaco"} />,
|
this.props.hideInputs ? <></> : <CodeMirrorEditor {...props} editorType="codemirror" />,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
</CodeCell>
|
</CodeCell>
|
||||||
@@ -84,8 +84,8 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
|||||||
<RawCell id={id} contentRef={contentRef} cell_type="raw">
|
<RawCell id={id} contentRef={contentRef} cell_type="raw">
|
||||||
{{
|
{{
|
||||||
editor: {
|
editor: {
|
||||||
monaco: (props: PassedEditorProps) =>
|
codemirror: (props: PassedEditorProps) =>
|
||||||
this.props.hideInputs ? <></> : <MonacoEditor {...props} readOnly={true} editorType={"monaco"} />,
|
this.props.hideInputs ? <></> : <CodeMirrorEditor {...props} editorType="codemirror" />,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
</RawCell>
|
</RawCell>
|
||||||
|
|||||||
@@ -36,10 +36,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror-scroll, .CodeMirror-linenumber, .CodeMirror-gutters {
|
.CodeMirror-scroll {
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-scroll,
|
||||||
|
.CodeMirror-linenumber,
|
||||||
|
.CodeMirror-gutters {
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.CodeMirror {
|
||||||
|
height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
.nteract-cell:hover {
|
.nteract-cell:hover {
|
||||||
border: 1px solid @HoverColor;
|
border: 1px solid @HoverColor;
|
||||||
border-left: 3px solid @HoverColor;
|
border-left: 3px solid @HoverColor;
|
||||||
@@ -58,13 +68,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// White background when hovered or selected
|
// White background when hovered or selected
|
||||||
.nteract-cell:hover, .nteract-cell-container.selected .nteract-cell {
|
.nteract-cell:hover,
|
||||||
.CodeMirror-scroll, .CodeMirror-linenumber, .CodeMirror-gutters {
|
.nteract-cell-container.selected .nteract-cell {
|
||||||
|
.CodeMirror-scroll,
|
||||||
|
.CodeMirror-linenumber,
|
||||||
|
.CodeMirror-gutters {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror-linenumber {
|
.CodeMirror-linenumber {
|
||||||
color: #015CDA;
|
color: #015cda;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nteract-cell-outputs {
|
.nteract-cell-outputs {
|
||||||
@@ -101,10 +114,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Undo tree.less
|
// Undo tree.less
|
||||||
.expanded::before {
|
.expanded::before {
|
||||||
content: '';
|
content: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
.monaco-editor .monaco-list .main {
|
.monaco-editor .monaco-list .main {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { CellId } from "@nteract/commutable";
|
|||||||
import { CellType } from "@nteract/commutable/src";
|
import { CellType } from "@nteract/commutable/src";
|
||||||
import { actions, ContentRef, selectors } from "@nteract/core";
|
import { actions, ContentRef, selectors } from "@nteract/core";
|
||||||
import { Cells, CodeCell, RawCell } from "@nteract/stateful-components";
|
import { Cells, CodeCell, RawCell } from "@nteract/stateful-components";
|
||||||
import MonacoEditor from "@nteract/stateful-components/lib/inputs/connected-editors/monacoEditor";
|
import CodeMirrorEditor from "@nteract/stateful-components/lib/inputs/connected-editors/codemirror";
|
||||||
import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor";
|
import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { DndProvider } from "react-dnd";
|
import { DndProvider } from "react-dnd";
|
||||||
@@ -120,7 +120,9 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
|
|||||||
<CodeCell id={id} contentRef={contentRef} cell_type="code">
|
<CodeCell id={id} contentRef={contentRef} cell_type="code">
|
||||||
{{
|
{{
|
||||||
editor: {
|
editor: {
|
||||||
monaco: (props: PassedEditorProps) => <MonacoEditor {...props} editorType={"monaco"} />,
|
codemirror: (props: PassedEditorProps) => (
|
||||||
|
<CodeMirrorEditor {...props} editorType="codemirror" />
|
||||||
|
),
|
||||||
},
|
},
|
||||||
prompt: ({ id, contentRef }: { id: CellId; contentRef: ContentRef }) => (
|
prompt: ({ id, contentRef }: { id: CellId; contentRef: ContentRef }) => (
|
||||||
<Prompt id={id} contentRef={contentRef} isHovered={false}>
|
<Prompt id={id} contentRef={contentRef} isHovered={false}>
|
||||||
@@ -142,7 +144,9 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
|
|||||||
<MarkdownCell id={id} contentRef={contentRef} cell_type="markdown">
|
<MarkdownCell id={id} contentRef={contentRef} cell_type="markdown">
|
||||||
{{
|
{{
|
||||||
editor: {
|
editor: {
|
||||||
monaco: (props: PassedEditorProps) => <MonacoEditor {...props} editorType={"monaco"} />,
|
codemirror: (props: PassedEditorProps) => (
|
||||||
|
<CodeMirrorEditor {...props} editorType="codemirror" />
|
||||||
|
),
|
||||||
},
|
},
|
||||||
toolbar: () => <CellToolbar id={id} contentRef={contentRef} />,
|
toolbar: () => <CellToolbar id={id} contentRef={contentRef} />,
|
||||||
}}
|
}}
|
||||||
@@ -157,7 +161,9 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
|
|||||||
<RawCell id={id} contentRef={contentRef} cell_type="raw">
|
<RawCell id={id} contentRef={contentRef} cell_type="raw">
|
||||||
{{
|
{{
|
||||||
editor: {
|
editor: {
|
||||||
monaco: (props: PassedEditorProps) => <MonacoEditor {...props} editorType={"monaco"} />,
|
codemirror: (props: PassedEditorProps) => (
|
||||||
|
<CodeMirrorEditor {...props} editorType="codemirror" />
|
||||||
|
),
|
||||||
},
|
},
|
||||||
toolbar: () => <CellToolbar id={id} contentRef={contentRef} />,
|
toolbar: () => <CellToolbar id={id} contentRef={contentRef} />,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
jest.mock("./NotebookComponent/store");
|
jest.mock("./NotebookComponent/store");
|
||||||
jest.mock("@nteract/core");
|
jest.mock("@nteract/core");
|
||||||
|
import { defineConfigOption } from "@nteract/mythic-configuration";
|
||||||
import { NotebookClientV2 } from "./NotebookClientV2";
|
import { NotebookClientV2 } from "./NotebookClientV2";
|
||||||
import configureStore from "./NotebookComponent/store";
|
import configureStore from "./NotebookComponent/store";
|
||||||
import { defineConfigOption } from "@nteract/mythic-configuration";
|
|
||||||
|
|
||||||
describe("auto start kernel", () => {
|
describe("auto start kernel", () => {
|
||||||
it("configure autoStartKernelOnNotebookOpen properly depending whether notebook is/is not read-only", async () => {
|
it("configure autoStartKernelOnNotebookOpen properly depending whether notebook is/is not read-only", async () => {
|
||||||
@@ -24,6 +24,12 @@ describe("auto start kernel", () => {
|
|||||||
defaultValue: 1234,
|
defaultValue: 1234,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defineConfigOption({
|
||||||
|
label: "Line numbers",
|
||||||
|
key: "codeMirror.lineNumbers",
|
||||||
|
defaultValue: true,
|
||||||
|
});
|
||||||
|
|
||||||
[true, false].forEach((isReadOnly) => {
|
[true, false].forEach((isReadOnly) => {
|
||||||
new NotebookClientV2({
|
new NotebookClientV2({
|
||||||
connectionInfo: {
|
connectionInfo: {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import { ActionContracts } from "../Contracts/ExplorerContracts";
|
import { ActionContracts } from "../../Contracts/ExplorerContracts";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import Explorer from "./Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { handleOpenAction } from "./OpenActions";
|
import { handleOpenAction } from "./OpenActions";
|
||||||
|
|
||||||
describe("OpenActions", () => {
|
describe("OpenActions", () => {
|
||||||
@@ -9,7 +9,6 @@ describe("OpenActions", () => {
|
|||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
let database: ViewModels.Database;
|
let database: ViewModels.Database;
|
||||||
let collection: ViewModels.Collection;
|
let collection: ViewModels.Collection;
|
||||||
let databases: ViewModels.Database[];
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = {} as Explorer;
|
explorer = {} as Explorer;
|
||||||
@@ -19,7 +18,6 @@ describe("OpenActions", () => {
|
|||||||
id: ko.observable("db"),
|
id: ko.observable("db"),
|
||||||
collections: ko.observableArray<ViewModels.Collection>([]),
|
collections: ko.observableArray<ViewModels.Collection>([]),
|
||||||
} as ViewModels.Database;
|
} as ViewModels.Database;
|
||||||
databases = [database];
|
|
||||||
collection = {
|
collection = {
|
||||||
id: ko.observable("coll"),
|
id: ko.observable("coll"),
|
||||||
} as ViewModels.Collection;
|
} as ViewModels.Collection;
|
||||||
@@ -68,7 +66,7 @@ describe("OpenActions", () => {
|
|||||||
paneKind: "AddCollection",
|
paneKind: "AddCollection",
|
||||||
};
|
};
|
||||||
|
|
||||||
const actionHandled = handleOpenAction(action, [], explorer);
|
handleOpenAction(action, [], explorer);
|
||||||
expect(explorer.onNewCollectionClicked).toHaveBeenCalled();
|
expect(explorer.onNewCollectionClicked).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -78,7 +76,7 @@ describe("OpenActions", () => {
|
|||||||
paneKind: ActionContracts.PaneKind.AddCollection,
|
paneKind: ActionContracts.PaneKind.AddCollection,
|
||||||
};
|
};
|
||||||
|
|
||||||
const actionHandled = handleOpenAction(action, [], explorer);
|
handleOpenAction(action, [], explorer);
|
||||||
expect(explorer.onNewCollectionClicked).toHaveBeenCalled();
|
expect(explorer.onNewCollectionClicked).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1,39 +1,38 @@
|
|||||||
// TODO convert this file to an action registry in order to have actions and their handlers be more tightly coupled.
|
// TODO convert this file to an action registry in order to have actions and their handlers be more tightly coupled.
|
||||||
|
import React from "react";
|
||||||
|
import { ActionContracts } from "../../Contracts/ExplorerContracts";
|
||||||
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
|
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||||
|
import Explorer from "../Explorer";
|
||||||
|
import { CassandraAddCollectionPane } from "../Panes/CassandraAddCollectionPane/CassandraAddCollectionPane";
|
||||||
|
import { SettingsPane } from "../Panes/SettingsPane/SettingsPane";
|
||||||
|
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
|
||||||
|
|
||||||
import { ActionContracts } from "../Contracts/ExplorerContracts";
|
function generateQueryText(action: ActionContracts.OpenQueryTab, partitionKeyProperty: string): string {
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
if (!action.query) {
|
||||||
import Explorer from "./Explorer";
|
return "SELECT * FROM c";
|
||||||
|
} else if (action.query.text) {
|
||||||
export function handleOpenAction(
|
return action.query.text;
|
||||||
action: ActionContracts.DataExplorerAction,
|
} else if (!!action.query.partitionKeys && action.query.partitionKeys.length > 0) {
|
||||||
databases: ViewModels.Database[],
|
let query = "SELECT * FROM c WHERE";
|
||||||
explorer: Explorer
|
for (let i = 0; i < action.query.partitionKeys.length; i++) {
|
||||||
): boolean {
|
const partitionKey = action.query.partitionKeys[i];
|
||||||
if (
|
if (!partitionKey) {
|
||||||
action.actionType === ActionContracts.ActionType.OpenCollectionTab ||
|
// null partition key case
|
||||||
(<any>action).actionType === ActionContracts.ActionType[ActionContracts.ActionType.OpenCollectionTab]
|
query = query.concat(` c.${partitionKeyProperty} = ${action.query.partitionKeys[i]}`);
|
||||||
) {
|
} else if (typeof partitionKey !== "string") {
|
||||||
openCollectionTab(<ActionContracts.OpenCollectionTab>action, databases);
|
// Undefined partition key case
|
||||||
return true;
|
query = query.concat(` NOT IS_DEFINED(c.${partitionKeyProperty})`);
|
||||||
|
} else {
|
||||||
|
query = query.concat(` c.${partitionKeyProperty} = "${action.query.partitionKeys[i]}"`);
|
||||||
}
|
}
|
||||||
|
if (i !== action.query.partitionKeys.length - 1) {
|
||||||
if (
|
query = query.concat(" OR");
|
||||||
action.actionType === ActionContracts.ActionType.OpenPane ||
|
|
||||||
(<any>action).actionType === ActionContracts.ActionType[ActionContracts.ActionType.OpenPane]
|
|
||||||
) {
|
|
||||||
openPane(<ActionContracts.OpenPane>action, explorer);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
action.actionType === ActionContracts.ActionType.OpenSampleNotebook ||
|
|
||||||
(<any>action).actionType === ActionContracts.ActionType[ActionContracts.ActionType.OpenSampleNotebook]
|
|
||||||
) {
|
|
||||||
openFile(<ActionContracts.OpenSampleNotebook>action, explorer);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
return query;
|
||||||
return false;
|
}
|
||||||
|
return "SELECT * FROM c";
|
||||||
}
|
}
|
||||||
|
|
||||||
function openCollectionTab(
|
function openCollectionTab(
|
||||||
@@ -65,7 +64,7 @@ function openCollectionTab(
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
action.tabKind === ActionContracts.TabKind.SQLDocuments ||
|
action.tabKind === ActionContracts.TabKind.SQLDocuments ||
|
||||||
(<any>action).tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLDocuments]
|
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLDocuments]
|
||||||
) {
|
) {
|
||||||
collection.onDocumentDBDocumentsClick();
|
collection.onDocumentDBDocumentsClick();
|
||||||
break;
|
break;
|
||||||
@@ -73,7 +72,7 @@ function openCollectionTab(
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
action.tabKind === ActionContracts.TabKind.MongoDocuments ||
|
action.tabKind === ActionContracts.TabKind.MongoDocuments ||
|
||||||
(<any>action).tabKind === ActionContracts.TabKind[ActionContracts.TabKind.MongoDocuments]
|
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.MongoDocuments]
|
||||||
) {
|
) {
|
||||||
collection.onMongoDBDocumentsClick();
|
collection.onMongoDBDocumentsClick();
|
||||||
break;
|
break;
|
||||||
@@ -81,7 +80,7 @@ function openCollectionTab(
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
action.tabKind === ActionContracts.TabKind.SchemaAnalyzer ||
|
action.tabKind === ActionContracts.TabKind.SchemaAnalyzer ||
|
||||||
(<any>action).tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SchemaAnalyzer]
|
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SchemaAnalyzer]
|
||||||
) {
|
) {
|
||||||
collection.onSchemaAnalyzerClick();
|
collection.onSchemaAnalyzerClick();
|
||||||
break;
|
break;
|
||||||
@@ -89,7 +88,7 @@ function openCollectionTab(
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
action.tabKind === ActionContracts.TabKind.TableEntities ||
|
action.tabKind === ActionContracts.TabKind.TableEntities ||
|
||||||
(<any>action).tabKind === ActionContracts.TabKind[ActionContracts.TabKind.TableEntities]
|
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.TableEntities]
|
||||||
) {
|
) {
|
||||||
collection.onTableEntitiesClick();
|
collection.onTableEntitiesClick();
|
||||||
break;
|
break;
|
||||||
@@ -97,7 +96,7 @@ function openCollectionTab(
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
action.tabKind === ActionContracts.TabKind.Graph ||
|
action.tabKind === ActionContracts.TabKind.Graph ||
|
||||||
(<any>action).tabKind === ActionContracts.TabKind[ActionContracts.TabKind.Graph]
|
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.Graph]
|
||||||
) {
|
) {
|
||||||
collection.onGraphDocumentsClick();
|
collection.onGraphDocumentsClick();
|
||||||
break;
|
break;
|
||||||
@@ -105,19 +104,19 @@ function openCollectionTab(
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
action.tabKind === ActionContracts.TabKind.SQLQuery ||
|
action.tabKind === ActionContracts.TabKind.SQLQuery ||
|
||||||
(<any>action).tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLQuery]
|
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLQuery]
|
||||||
) {
|
) {
|
||||||
collection.onNewQueryClick(
|
collection.onNewQueryClick(
|
||||||
collection,
|
collection,
|
||||||
null,
|
undefined,
|
||||||
generateQueryText(<ActionContracts.OpenQueryTab>action, collection.partitionKeyProperty)
|
generateQueryText(action as ActionContracts.OpenQueryTab, collection.partitionKeyProperty)
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
action.tabKind === ActionContracts.TabKind.ScaleSettings ||
|
action.tabKind === ActionContracts.TabKind.ScaleSettings ||
|
||||||
(<any>action).tabKind === ActionContracts.TabKind[ActionContracts.TabKind.ScaleSettings]
|
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.ScaleSettings]
|
||||||
) {
|
) {
|
||||||
collection.onSettingsClick();
|
collection.onSettingsClick();
|
||||||
break;
|
break;
|
||||||
@@ -138,49 +137,59 @@ function openCollectionTab(
|
|||||||
function openPane(action: ActionContracts.OpenPane, explorer: Explorer) {
|
function openPane(action: ActionContracts.OpenPane, explorer: Explorer) {
|
||||||
if (
|
if (
|
||||||
action.paneKind === ActionContracts.PaneKind.AddCollection ||
|
action.paneKind === ActionContracts.PaneKind.AddCollection ||
|
||||||
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.AddCollection]
|
action.paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.AddCollection]
|
||||||
) {
|
) {
|
||||||
explorer.onNewCollectionClicked();
|
explorer.onNewCollectionClicked();
|
||||||
} else if (
|
} else if (
|
||||||
action.paneKind === ActionContracts.PaneKind.CassandraAddCollection ||
|
action.paneKind === ActionContracts.PaneKind.CassandraAddCollection ||
|
||||||
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.CassandraAddCollection]
|
action.paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.CassandraAddCollection]
|
||||||
) {
|
) {
|
||||||
explorer.openCassandraAddCollectionPane();
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel(
|
||||||
|
"Add Table",
|
||||||
|
<CassandraAddCollectionPane explorer={explorer} cassandraApiClient={new CassandraAPIDataClient()} />
|
||||||
|
);
|
||||||
} else if (
|
} else if (
|
||||||
action.paneKind === ActionContracts.PaneKind.GlobalSettings ||
|
action.paneKind === ActionContracts.PaneKind.GlobalSettings ||
|
||||||
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.GlobalSettings]
|
action.paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.GlobalSettings]
|
||||||
) {
|
) {
|
||||||
explorer.openSettingPane();
|
useSidePanel.getState().openSidePanel("Settings", <SettingsPane />);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function handleOpenAction(
|
||||||
|
action: ActionContracts.DataExplorerAction,
|
||||||
|
databases: ViewModels.Database[],
|
||||||
|
explorer: Explorer
|
||||||
|
): boolean {
|
||||||
|
if (
|
||||||
|
action.actionType === ActionContracts.ActionType.OpenCollectionTab ||
|
||||||
|
action.actionType === ActionContracts.ActionType[ActionContracts.ActionType.OpenCollectionTab]
|
||||||
|
) {
|
||||||
|
openCollectionTab(action as ActionContracts.OpenCollectionTab, databases);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
action.actionType === ActionContracts.ActionType.OpenPane ||
|
||||||
|
action.actionType === ActionContracts.ActionType[ActionContracts.ActionType.OpenPane]
|
||||||
|
) {
|
||||||
|
openPane(action as ActionContracts.OpenPane, explorer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
action.actionType === ActionContracts.ActionType.OpenSampleNotebook ||
|
||||||
|
action.actionType === ActionContracts.ActionType[ActionContracts.ActionType.OpenSampleNotebook]
|
||||||
|
) {
|
||||||
|
openFile(action as ActionContracts.OpenSampleNotebook, explorer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function openFile(action: ActionContracts.OpenSampleNotebook, explorer: Explorer) {
|
function openFile(action: ActionContracts.OpenSampleNotebook, explorer: Explorer) {
|
||||||
explorer.handleOpenFileAction(decodeURIComponent(action.path));
|
explorer.handleOpenFileAction(decodeURIComponent(action.path));
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateQueryText(action: ActionContracts.OpenQueryTab, partitionKeyProperty: string): string {
|
|
||||||
if (!action.query) {
|
|
||||||
return "SELECT * FROM c";
|
|
||||||
} else if (!!action.query.text) {
|
|
||||||
return action.query.text;
|
|
||||||
} else if (!!action.query.partitionKeys && action.query.partitionKeys.length > 0) {
|
|
||||||
let query = "SELECT * FROM c WHERE";
|
|
||||||
for (let i = 0; i < action.query.partitionKeys.length; i++) {
|
|
||||||
let partitionKey = action.query.partitionKeys[i];
|
|
||||||
if (!partitionKey) {
|
|
||||||
// null partition key case
|
|
||||||
query = query.concat(` c.${partitionKeyProperty} = ${action.query.partitionKeys[i]}`);
|
|
||||||
} else if (typeof partitionKey !== "string") {
|
|
||||||
// Undefined partition key case
|
|
||||||
query = query.concat(` NOT IS_DEFINED(c.${partitionKeyProperty})`);
|
|
||||||
} else {
|
|
||||||
query = query.concat(` c.${partitionKeyProperty} = "${action.query.partitionKeys[i]}"`);
|
|
||||||
}
|
|
||||||
if (i !== action.query.partitionKeys.length - 1) {
|
|
||||||
query = query.concat(" OR");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
return "SELECT * FROM c";
|
|
||||||
}
|
|
||||||
@@ -31,6 +31,7 @@ import { getUpsellMessage } from "../../Utils/PricingUtils";
|
|||||||
import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
||||||
import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput";
|
import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
|
import { useDatabases } from "../useDatabases";
|
||||||
import { PanelFooterComponent } from "./PanelFooterComponent";
|
import { PanelFooterComponent } from "./PanelFooterComponent";
|
||||||
import { PanelInfoErrorComponent } from "./PanelInfoErrorComponent";
|
import { PanelInfoErrorComponent } from "./PanelInfoErrorComponent";
|
||||||
import { PanelLoadingScreen } from "./PanelLoadingScreen";
|
import { PanelLoadingScreen } from "./PanelLoadingScreen";
|
||||||
@@ -125,6 +126,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
}
|
}
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
|
const isFirstResourceCreated = useDatabases.getState().isFirstResourceCreated();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="panelFormWrapper" onSubmit={this.submit.bind(this)}>
|
<form className="panelFormWrapper" onSubmit={this.submit.bind(this)}>
|
||||||
{this.state.errorMessage && (
|
{this.state.errorMessage && (
|
||||||
@@ -137,7 +140,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
|
|
||||||
{!this.state.errorMessage && this.isFreeTierAccount() && (
|
{!this.state.errorMessage && this.isFreeTierAccount() && (
|
||||||
<PanelInfoErrorComponent
|
<PanelInfoErrorComponent
|
||||||
message={getUpsellMessage(userContext.portalEnv, true, this.props.explorer.isFirstResourceCreated(), true)}
|
message={getUpsellMessage(userContext.portalEnv, true, isFirstResourceCreated, true)}
|
||||||
messageType="info"
|
messageType="info"
|
||||||
showErrorDetails={false}
|
showErrorDetails={false}
|
||||||
link={Constants.Urls.freeTierInformation}
|
link={Constants.Urls.freeTierInformation}
|
||||||
@@ -240,9 +243,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
|
|
||||||
{!isServerlessAccount() && this.state.isSharedThroughputChecked && (
|
{!isServerlessAccount() && this.state.isSharedThroughputChecked && (
|
||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
showFreeTierExceedThroughputTooltip={
|
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated}
|
||||||
this.isFreeTierAccount() && !this.props.explorer.isFirstResourceCreated()
|
|
||||||
}
|
|
||||||
isDatabase={true}
|
isDatabase={true}
|
||||||
isSharded={this.state.isSharded}
|
isSharded={this.state.isSharded}
|
||||||
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
|
||||||
@@ -469,9 +470,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
|
|
||||||
{this.shouldShowCollectionThroughputInput() && (
|
{this.shouldShowCollectionThroughputInput() && (
|
||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
showFreeTierExceedThroughputTooltip={
|
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated}
|
||||||
this.isFreeTierAccount() && !this.props.explorer.isFirstResourceCreated()
|
|
||||||
}
|
|
||||||
isDatabase={false}
|
isDatabase={false}
|
||||||
isSharded={this.state.isSharded}
|
isSharded={this.state.isSharded}
|
||||||
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
|
||||||
@@ -680,7 +679,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getDatabaseOptions(): IDropdownOption[] {
|
private getDatabaseOptions(): IDropdownOption[] {
|
||||||
return this.props.explorer?.databases()?.map((database) => ({
|
return useDatabases.getState().databases?.map((database) => ({
|
||||||
key: database.id(),
|
key: database.id(),
|
||||||
text: database.id(),
|
text: database.id(),
|
||||||
}));
|
}));
|
||||||
@@ -772,9 +771,9 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedDatabase = this.props.explorer
|
const selectedDatabase = useDatabases
|
||||||
.databases()
|
.getState()
|
||||||
?.find((database) => database.id() === this.state.selectedDatabaseId);
|
.databases?.find((database) => database.id() === this.state.selectedDatabaseId);
|
||||||
return !!selectedDatabase?.offer();
|
return !!selectedDatabase?.offer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ import { isServerlessAccount } from "../../../Utils/CapabilityUtils";
|
|||||||
import { getUpsellMessage } from "../../../Utils/PricingUtils";
|
import { getUpsellMessage } from "../../../Utils/PricingUtils";
|
||||||
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
|
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
|
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
|
||||||
|
import { getTextFieldStyles } from "../PanelStyles";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
export interface AddDatabasePaneProps {
|
export interface AddDatabasePaneProps {
|
||||||
@@ -171,7 +173,12 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
<RightPaneForm {...props}>
|
<RightPaneForm {...props}>
|
||||||
{!formErrors && isFreeTierAccount && (
|
{!formErrors && isFreeTierAccount && (
|
||||||
<PanelInfoErrorComponent
|
<PanelInfoErrorComponent
|
||||||
message={getUpsellMessage(userContext.portalEnv, true, container.isFirstResourceCreated(), true)}
|
message={getUpsellMessage(
|
||||||
|
userContext.portalEnv,
|
||||||
|
true,
|
||||||
|
useDatabases.getState().isFirstResourceCreated(),
|
||||||
|
true
|
||||||
|
)}
|
||||||
messageType="info"
|
messageType="info"
|
||||||
showErrorDetails={false}
|
showErrorDetails={false}
|
||||||
link={Constants.Urls.freeTierInformation}
|
link={Constants.Urls.freeTierInformation}
|
||||||
@@ -179,10 +186,12 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
<div>
|
<Stack>
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<span className="mandatoryStar">*</span>
|
<span className="mandatoryStar">* </span>
|
||||||
<Text variant="small">{databaseIdLabel}</Text>
|
<Text className="panelTextBold" variant="small">
|
||||||
|
{databaseIdLabel}
|
||||||
|
</Text>
|
||||||
<InfoTooltip>{databaseIdTooltipText}</InfoTooltip>
|
<InfoTooltip>{databaseIdTooltipText}</InfoTooltip>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
@@ -199,10 +208,10 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
value={databaseId}
|
value={databaseId}
|
||||||
onChange={handleonChangeDBId}
|
onChange={handleonChangeDBId}
|
||||||
autoFocus
|
autoFocus
|
||||||
style={{ fontSize: 12 }}
|
styles={getTextFieldStyles()}
|
||||||
styles={{ root: { width: 300 } }}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{!isServerlessAccount() && (
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
title="Provision shared throughput"
|
title="Provision shared throughput"
|
||||||
@@ -217,10 +226,12 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
/>
|
/>
|
||||||
<InfoTooltip>{databaseLevelThroughputTooltipText}</InfoTooltip>
|
<InfoTooltip>{databaseLevelThroughputTooltipText}</InfoTooltip>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
|
||||||
{!isServerlessAccount() && databaseCreateNewShared && (
|
{!isServerlessAccount() && databaseCreateNewShared && (
|
||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container?.isFirstResourceCreated()}
|
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
|
||||||
isDatabase={true}
|
isDatabase={true}
|
||||||
isSharded={databaseCreateNewShared}
|
isSharded={databaseCreateNewShared}
|
||||||
setThroughputValue={(newThroughput: number) => (throughput = newThroughput)}
|
setThroughputValue={(newThroughput: number) => (throughput = newThroughput)}
|
||||||
@@ -229,7 +240,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</RightPaneForm>
|
</RightPaneForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,16 +10,17 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
|||||||
<div
|
<div
|
||||||
className="panelMainContent"
|
className="panelMainContent"
|
||||||
>
|
>
|
||||||
<div>
|
<Stack>
|
||||||
<Stack
|
<Stack
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="mandatoryStar"
|
className="mandatoryStar"
|
||||||
>
|
>
|
||||||
*
|
*
|
||||||
</span>
|
</span>
|
||||||
<Text
|
<Text
|
||||||
|
className="panelTextBold"
|
||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
Database id
|
Database id
|
||||||
@@ -38,13 +39,16 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
|||||||
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
|
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
|
||||||
placeholder="Type a new database id"
|
placeholder="Type a new database id"
|
||||||
size={40}
|
size={40}
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"fontSize": 12,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
|
"field": Object {
|
||||||
|
"fontSize": 12,
|
||||||
|
"selectors": Object {
|
||||||
|
"::placeholder": Object {
|
||||||
|
"fontSize": 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"width": 300,
|
"width": 300,
|
||||||
},
|
},
|
||||||
@@ -82,6 +86,7 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
|||||||
Provisioned throughput at the database level will be shared across all collections within the database.
|
Provisioned throughput at the database level will be shared across all collections within the database.
|
||||||
</InfoTooltip>
|
</InfoTooltip>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</Stack>
|
||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
isDatabase={true}
|
isDatabase={true}
|
||||||
isSharded={true}
|
isSharded={true}
|
||||||
@@ -90,6 +95,5 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
|||||||
setThroughputValue={[Function]}
|
setThroughputValue={[Function]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</RightPaneForm>
|
</RightPaneForm>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import { mount } from "enzyme";
|
import { mount } from "enzyme";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { SavedQueries } from "../../../Common/Constants";
|
||||||
import { QueriesClient } from "../../../Common/QueriesClient";
|
import { QueriesClient } from "../../../Common/QueriesClient";
|
||||||
import { Query } from "../../../Contracts/DataModels";
|
import { Query } from "../../../Contracts/DataModels";
|
||||||
|
import { Collection, Database } from "../../../Contracts/ViewModels";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { BrowseQueriesPane } from "./BrowseQueriesPane";
|
import { BrowseQueriesPane } from "./BrowseQueriesPane";
|
||||||
|
|
||||||
describe("Browse queries panel", () => {
|
describe("Browse queries panel", () => {
|
||||||
const fakeExplorer = {} as Explorer;
|
const fakeExplorer = {} as Explorer;
|
||||||
fakeExplorer.canSaveQueries = ko.computed<boolean>(() => true);
|
|
||||||
const fakeClientQuery = {} as QueriesClient;
|
const fakeClientQuery = {} as QueriesClient;
|
||||||
const fakeQueryData = [] as Query[];
|
const fakeQueryData = [] as Query[];
|
||||||
fakeClientQuery.getQueries = async () => fakeQueryData;
|
fakeClientQuery.getQueries = async () => fakeQueryData;
|
||||||
@@ -17,6 +19,16 @@ describe("Browse queries panel", () => {
|
|||||||
explorer: fakeExplorer,
|
explorer: fakeExplorer,
|
||||||
closePanel: (): void => undefined,
|
closePanel: (): void => undefined,
|
||||||
};
|
};
|
||||||
|
useDatabases.getState().addDatabases([
|
||||||
|
{
|
||||||
|
id: ko.observable(SavedQueries.DatabaseName),
|
||||||
|
collections: ko.observableArray([
|
||||||
|
{
|
||||||
|
id: ko.observable(SavedQueries.CollectionName),
|
||||||
|
} as Collection,
|
||||||
|
]),
|
||||||
|
} as Database,
|
||||||
|
]);
|
||||||
|
|
||||||
it("Should render Default properly", () => {
|
it("Should render Default properly", () => {
|
||||||
const wrapper = mount(<BrowseQueriesPane {...props} />);
|
const wrapper = mount(<BrowseQueriesPane {...props} />);
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import {
|
|||||||
QueriesGridComponentProps,
|
QueriesGridComponentProps,
|
||||||
} from "../../Controls/QueriesGridReactComponent/QueriesGridComponent";
|
} from "../../Controls/QueriesGridReactComponent/QueriesGridComponent";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import QueryTab from "../../Tabs/QueryTab";
|
import { NewQueryTab } from "../../Tabs/QueryTab/QueryTab";
|
||||||
|
import { useDatabases } from "../../useDatabases";
|
||||||
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
|
|
||||||
interface BrowseQueriesPaneProps {
|
interface BrowseQueriesPaneProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
@@ -23,7 +25,7 @@ export const BrowseQueriesPane: FunctionComponent<BrowseQueriesPaneProps> = ({
|
|||||||
}: BrowseQueriesPaneProps): JSX.Element => {
|
}: BrowseQueriesPaneProps): JSX.Element => {
|
||||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
const loadSavedQuery = (savedQuery: Query): void => {
|
const loadSavedQuery = (savedQuery: Query): void => {
|
||||||
const selectedCollection: Collection = explorer && explorer.findSelectedCollection();
|
const selectedCollection: Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
if (!selectedCollection) {
|
if (!selectedCollection) {
|
||||||
// should never get into this state because this pane is only accessible through the query tab
|
// should never get into this state because this pane is only accessible through the query tab
|
||||||
logError("No collection was selected", "BrowseQueriesPane.loadSavedQuery");
|
logError("No collection was selected", "BrowseQueriesPane.loadSavedQuery");
|
||||||
@@ -31,13 +33,13 @@ export const BrowseQueriesPane: FunctionComponent<BrowseQueriesPaneProps> = ({
|
|||||||
} else if (userContext.apiType === "Mongo") {
|
} else if (userContext.apiType === "Mongo") {
|
||||||
selectedCollection.onNewMongoQueryClick(selectedCollection, undefined);
|
selectedCollection.onNewMongoQueryClick(selectedCollection, undefined);
|
||||||
} else {
|
} else {
|
||||||
selectedCollection.onNewQueryClick(selectedCollection, undefined);
|
selectedCollection.onNewQueryClick(selectedCollection, undefined, savedQuery.query);
|
||||||
}
|
}
|
||||||
const queryTab = explorer.tabsManager.activeTab() as QueryTab;
|
|
||||||
|
const queryTab = explorer && (explorer.tabsManager.activeTab() as NewQueryTab);
|
||||||
queryTab.tabTitle(savedQuery.queryName);
|
queryTab.tabTitle(savedQuery.queryName);
|
||||||
queryTab.tabPath(`${selectedCollection.databaseId}>${selectedCollection.id()}>${savedQuery.queryName}`);
|
queryTab.tabPath(`${selectedCollection.databaseId}>${selectedCollection.id()}>${savedQuery.queryName}`);
|
||||||
queryTab.initialEditorContent(savedQuery.query);
|
|
||||||
queryTab.sqlQueryEditorContent(savedQuery.query);
|
|
||||||
trace(Action.LoadSavedQuery, ActionModifiers.Mark, {
|
trace(Action.LoadSavedQuery, ActionModifiers.Mark, {
|
||||||
dataExplorerArea: Areas.ContextualPane,
|
dataExplorerArea: Areas.ContextualPane,
|
||||||
queryName: savedQuery.queryName,
|
queryName: savedQuery.queryName,
|
||||||
@@ -45,12 +47,13 @@ export const BrowseQueriesPane: FunctionComponent<BrowseQueriesPaneProps> = ({
|
|||||||
});
|
});
|
||||||
closeSidePanel();
|
closeSidePanel();
|
||||||
};
|
};
|
||||||
|
const isSaveQueryEnabled = useDatabases((state) => state.isSaveQueryEnabled);
|
||||||
|
|
||||||
const props: QueriesGridComponentProps = {
|
const props: QueriesGridComponentProps = {
|
||||||
queriesClient: explorer.queriesClient,
|
queriesClient: explorer.queriesClient,
|
||||||
onQuerySelect: loadSavedQuery,
|
onQuerySelect: loadSavedQuery,
|
||||||
containerVisible: true,
|
containerVisible: true,
|
||||||
saveQueryEnabled: explorer.canSaveQueries(),
|
saveQueryEnabled: isSaveQueryEnabled(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ exports[`Browse queries panel Should render Default properly 1`] = `
|
|||||||
closePanel={[Function]}
|
closePanel={[Function]}
|
||||||
explorer={
|
explorer={
|
||||||
Object {
|
Object {
|
||||||
"canSaveQueries": [Function],
|
|
||||||
"queriesClient": Object {
|
"queriesClient": Object {
|
||||||
"getQueries": [Function],
|
"getQueries": [Function],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,32 +1,30 @@
|
|||||||
import { fireEvent, render, screen } from "@testing-library/react";
|
import { fireEvent, render, screen } from "@testing-library/react";
|
||||||
import { shallow } from "enzyme";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
|
import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
|
||||||
import { CassandraAddCollectionPane } from "./CassandraAddCollectionPane";
|
import { CassandraAddCollectionPane } from "./CassandraAddCollectionPane";
|
||||||
|
|
||||||
|
describe("Cassandra add collection pane test", () => {
|
||||||
const props = {
|
const props = {
|
||||||
explorer: new Explorer(),
|
explorer: new Explorer(),
|
||||||
closePanel: (): void => undefined,
|
closePanel: (): void => undefined,
|
||||||
cassandraApiClient: new CassandraAPIDataClient(),
|
cassandraApiClient: new CassandraAPIDataClient(),
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("CassandraAddCollectionPane Pane", () => {
|
|
||||||
beforeEach(() => render(<CassandraAddCollectionPane {...props} />));
|
beforeEach(() => render(<CassandraAddCollectionPane {...props} />));
|
||||||
|
|
||||||
it("should render Default properly", () => {
|
it("should render default properly", () => {
|
||||||
const wrapper = shallow(<CassandraAddCollectionPane {...props} />);
|
expect(screen.getByRole("radio", { name: "Create new keyspace", checked: true })).toBeDefined();
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(screen.getByRole("checkbox", { name: "Provision shared throughput", checked: false })).toBeDefined();
|
||||||
});
|
|
||||||
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 ", () => {
|
it("click on use existing", () => {
|
||||||
fireEvent.change(screen.getByLabelText("Keyspace id"), { target: { value: "unittest1" } });
|
fireEvent.click(screen.getByRole("radio", { name: "Use existing keyspace" }));
|
||||||
expect(screen.getByLabelText("CREATE TABLE unittest1.")).toBeDefined();
|
expect(screen.getByRole("combobox", { name: "Choose existing keyspace id" })).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("enter Keyspace name ", () => {
|
||||||
|
fireEvent.change(screen.getByRole("textbox", { name: "Keyspace id" }), { target: { value: "table1" } });
|
||||||
|
expect(screen.getByText("CREATE TABLE table1.")).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
import { Label, Stack, TextField } from "@fluentui/react";
|
import { Checkbox, Dropdown, IDropdownOption, Link, Stack, Text, TextField } from "@fluentui/react";
|
||||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
import * as _ from "underscore";
|
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||||
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
|
||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import * as AddCollectionUtility from "../../../Shared/AddCollectionUtility";
|
|
||||||
import * as SharedConstants from "../../../Shared/Constants";
|
import * as SharedConstants from "../../../Shared/Constants";
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
import { isServerlessAccount } from "../../../Utils/CapabilityUtils";
|
||||||
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
|
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
|
import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
|
||||||
|
import { useDatabases } from "../../useDatabases";
|
||||||
|
import { getTextFieldStyles } from "../PanelStyles";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
export interface CassandraAddCollectionPaneProps {
|
export interface CassandraAddCollectionPaneProps {
|
||||||
@@ -27,183 +25,73 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
explorer: container,
|
explorer: container,
|
||||||
cassandraApiClient,
|
cassandraApiClient,
|
||||||
}: CassandraAddCollectionPaneProps) => {
|
}: CassandraAddCollectionPaneProps) => {
|
||||||
|
let newKeySpaceThroughput: number;
|
||||||
|
let isNewKeySpaceAutoscale: boolean;
|
||||||
|
let tableThroughput: number;
|
||||||
|
let isTableAutoscale: boolean;
|
||||||
|
let isCostAcknowledged: boolean;
|
||||||
|
|
||||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
const throughputDefaults = userContext.collectionCreationDefaults.throughput;
|
const [newKeyspaceId, setNewKeyspaceId] = useState<string>("");
|
||||||
const [createTableQuery, setCreateTableQuery] = useState<string>("CREATE TABLE ");
|
const [existingKeyspaceId, setExistingKeyspaceId] = useState<string>("");
|
||||||
const [keyspaceId, setKeyspaceId] = useState<string>("");
|
|
||||||
const [tableId, setTableId] = useState<string>("");
|
const [tableId, setTableId] = useState<string>("");
|
||||||
const [throughput, setThroughput] = useState<number>(
|
|
||||||
AddCollectionUtility.getMaxThroughput(userContext.collectionCreationDefaults, container)
|
|
||||||
);
|
|
||||||
|
|
||||||
const [isAutoPilotSelected, setIsAutoPilotSelected] = useState<boolean>(userContext.features.autoscaleDefault);
|
|
||||||
|
|
||||||
const [isSharedAutoPilotSelected, setIsSharedAutoPilotSelected] = useState<boolean>(
|
|
||||||
userContext.features.autoscaleDefault
|
|
||||||
);
|
|
||||||
|
|
||||||
const [userTableQuery, setUserTableQuery] = useState<string>(
|
const [userTableQuery, setUserTableQuery] = useState<string>(
|
||||||
"(userid int, name text, email text, PRIMARY KEY (userid))"
|
"(userid int, name text, email text, PRIMARY KEY (userid))"
|
||||||
);
|
);
|
||||||
|
const [isKeyspaceShared, setIsKeyspaceShared] = useState<boolean>(false);
|
||||||
const [keyspaceHasSharedOffer, setKeyspaceHasSharedOffer] = useState<boolean>(false);
|
|
||||||
const [keyspaceIds, setKeyspaceIds] = useState<string[]>([]);
|
|
||||||
const [keyspaceThroughput, setKeyspaceThroughput] = useState<number>(throughputDefaults.shared);
|
|
||||||
const [keyspaceCreateNew, setKeyspaceCreateNew] = useState<boolean>(true);
|
const [keyspaceCreateNew, setKeyspaceCreateNew] = useState<boolean>(true);
|
||||||
const [dedicateTableThroughput, setDedicateTableThroughput] = useState<boolean>(false);
|
const [dedicateTableThroughput, setDedicateTableThroughput] = useState<boolean>(false);
|
||||||
const [throughputSpendAck, setThroughputSpendAck] = useState<boolean>(false);
|
|
||||||
const [sharedThroughputSpendAck, setSharedThroughputSpendAck] = useState<boolean>(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<boolean>();
|
const [isExecuting, setIsExecuting] = useState<boolean>();
|
||||||
const [formErrors, setFormErrors] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
|
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
|
||||||
useEffect(() => {
|
|
||||||
if (keyspaceIds.indexOf(keyspaceId) >= 0) {
|
|
||||||
setKeyspaceHasSharedOffer(keyspaceOffers.has(keyspaceId));
|
|
||||||
}
|
|
||||||
setCreateTableQuery(`CREATE TABLE ${keyspaceId}.`);
|
|
||||||
}, [keyspaceId]);
|
|
||||||
|
|
||||||
const addCollectionPaneOpenMessage = {
|
const addCollectionPaneOpenMessage = {
|
||||||
collection: {
|
collection: {
|
||||||
id: tableId,
|
id: tableId,
|
||||||
storage: Constants.BackendDefaults.multiPartitionStorageInGb,
|
storage: Constants.BackendDefaults.multiPartitionStorageInGb,
|
||||||
offerThroughput: throughput,
|
offerThroughput: newKeySpaceThroughput || tableThroughput,
|
||||||
partitionKey: "",
|
partitionKey: "",
|
||||||
databaseId: keyspaceId,
|
databaseId: keyspaceCreateNew ? newKeyspaceId : existingKeyspaceId,
|
||||||
},
|
},
|
||||||
subscriptionType: userContext.subscriptionType,
|
subscriptionType: userContext.subscriptionType,
|
||||||
subscriptionQuotaId: userContext.quotaId,
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: "u",
|
storage: "u",
|
||||||
throughput,
|
throughput: newKeySpaceThroughput || tableThroughput,
|
||||||
flight: userContext.addCollectionFlight,
|
flight: userContext.addCollectionFlight,
|
||||||
},
|
},
|
||||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!container.isServerlessEnabled()) {
|
|
||||||
setIsAutoPilotSelected(userContext.features.autoscaleDefault);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 () => {
|
const onSubmit = async () => {
|
||||||
if (!_isValid()) {
|
const throughput = keyspaceCreateNew ? newKeySpaceThroughput : tableThroughput;
|
||||||
|
const keyspaceId = keyspaceCreateNew ? newKeyspaceId : existingKeyspaceId;
|
||||||
|
|
||||||
|
if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !isCostAcknowledged) {
|
||||||
|
const errorMessage =
|
||||||
|
isNewKeySpaceAutoscale || isTableAutoscale
|
||||||
|
? "Please acknowledge the estimated monthly spend."
|
||||||
|
: "Please acknowledge the estimated daily spend.";
|
||||||
|
setFormError(errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsExecuting(true);
|
setIsExecuting(true);
|
||||||
const autoPilotCommand = `cosmosdb_autoscale_max_throughput`;
|
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 createKeyspaceQueryPrefix = `CREATE KEYSPACE ${keyspaceId.trim()} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 }`;
|
||||||
const createKeyspaceQuery: string = keyspaceHasSharedOffer
|
const createKeyspaceQuery: string = isKeyspaceShared
|
||||||
? useAutoPilotForKeyspace
|
? isNewKeySpaceAutoscale
|
||||||
? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${keyspaceThroughput};`
|
? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${newKeySpaceThroughput};`
|
||||||
: `${createKeyspaceQueryPrefix} AND cosmosdb_provisioned_throughput=${keyspaceThroughput};`
|
: `${createKeyspaceQueryPrefix} AND cosmosdb_provisioned_throughput=${newKeySpaceThroughput};`
|
||||||
: `${createKeyspaceQueryPrefix};`;
|
: `${createKeyspaceQueryPrefix};`;
|
||||||
let tableQuery: string;
|
let tableQuery: string;
|
||||||
const createTableQueryPrefix = `${createTableQuery}${tableId.trim()} ${userTableQuery}`;
|
const createTableQueryPrefix = `CREATE TABLE ${keyspaceId}.${tableId.trim()} ${userTableQuery}`;
|
||||||
|
|
||||||
if (canConfigureThroughput && (dedicateTableThroughput || !keyspaceHasSharedOffer)) {
|
if (tableThroughput) {
|
||||||
if (isAutoPilotSelected && selectedAutoPilotThroughput) {
|
if (isTableAutoscale) {
|
||||||
tableQuery = `${createTableQueryPrefix} WITH ${autoPilotCommand}=${throughput};`;
|
tableQuery = `${createTableQueryPrefix} WITH ${autoPilotCommand}=${tableThroughput};`;
|
||||||
} else {
|
} else {
|
||||||
tableQuery = `${createTableQueryPrefix} WITH cosmosdb_provisioned_throughput=${throughput};`;
|
tableQuery = `${createTableQueryPrefix} WITH cosmosdb_provisioned_throughput=${tableThroughput};`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tableQuery = `${createTableQueryPrefix};`;
|
tableQuery = `${createTableQueryPrefix};`;
|
||||||
@@ -215,15 +103,15 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
...addCollectionPaneOpenMessage.collection,
|
...addCollectionPaneOpenMessage.collection,
|
||||||
hasDedicatedThroughput: dedicateTableThroughput,
|
hasDedicatedThroughput: dedicateTableThroughput,
|
||||||
},
|
},
|
||||||
keyspaceHasSharedOffer,
|
isKeyspaceShared,
|
||||||
toCreateKeyspace,
|
keyspaceCreateNew,
|
||||||
createKeyspaceQuery,
|
createKeyspaceQuery,
|
||||||
createTableQuery: tableQuery,
|
createTableQuery: tableQuery,
|
||||||
};
|
};
|
||||||
|
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.CreateCollection, addCollectionPaneStartMessage);
|
const startKey: number = TelemetryProcessor.traceStart(Action.CreateCollection, addCollectionPaneStartMessage);
|
||||||
try {
|
try {
|
||||||
if (toCreateKeyspace) {
|
if (keyspaceCreateNew) {
|
||||||
await cassandraApiClient.createTableAndKeyspace(
|
await cassandraApiClient.createTableAndKeyspace(
|
||||||
userContext?.databaseAccount?.properties?.cassandraEndpoint,
|
userContext?.databaseAccount?.properties?.cassandraEndpoint,
|
||||||
userContext?.databaseAccount?.id,
|
userContext?.databaseAccount?.id,
|
||||||
@@ -246,7 +134,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
TelemetryProcessor.traceSuccess(Action.CreateCollection, addCollectionPaneStartMessage, startKey);
|
TelemetryProcessor.traceSuccess(Action.CreateCollection, addCollectionPaneStartMessage, startKey);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = getErrorMessage(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
setFormErrors(errorMessage);
|
setFormError(errorMessage);
|
||||||
setIsExecuting(false);
|
setIsExecuting(false);
|
||||||
const addCollectionPaneFailedMessage = {
|
const addCollectionPaneFailedMessage = {
|
||||||
...addCollectionPaneStartMessage,
|
...addCollectionPaneStartMessage,
|
||||||
@@ -256,116 +144,146 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
TelemetryProcessor.traceFailure(Action.CreateCollection, addCollectionPaneFailedMessage, startKey);
|
TelemetryProcessor.traceFailure(Action.CreateCollection, addCollectionPaneFailedMessage, startKey);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const handleOnChangeKeyspaceType = (ev: React.FormEvent<HTMLInputElement>, mode: string): void => {
|
|
||||||
setKeyspaceCreateNew(mode === "Create new");
|
|
||||||
};
|
|
||||||
|
|
||||||
const props: RightPaneFormProps = {
|
const props: RightPaneFormProps = {
|
||||||
formError: formErrors,
|
formError,
|
||||||
isExecuting,
|
isExecuting,
|
||||||
submitButtonText: "Apply",
|
submitButtonText: "OK",
|
||||||
onSubmit,
|
onSubmit,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<RightPaneForm {...props}>
|
||||||
<div className="paneMainContent">
|
<div className="panelMainContent">
|
||||||
<div className="seconddivpadding">
|
<Stack>
|
||||||
<p>
|
<Stack horizontal>
|
||||||
<Label required>
|
<span className="mandatoryStar">* </span>
|
||||||
|
<Text className="panelTextBold" variant="small">
|
||||||
Keyspace name <InfoTooltip>Select an existing keyspace or enter a new keyspace id.</InfoTooltip>
|
Keyspace name <InfoTooltip>Select an existing keyspace or enter a new keyspace id.</InfoTooltip>
|
||||||
</Label>
|
</Text>
|
||||||
</p>
|
</Stack>
|
||||||
|
|
||||||
<Stack horizontal verticalAlign="center">
|
<Stack horizontal verticalAlign="center">
|
||||||
<input
|
<input
|
||||||
className="throughputInputRadioBtn"
|
className="panelRadioBtn"
|
||||||
aria-label="Create new keyspace"
|
aria-label="Create new keyspace"
|
||||||
checked={keyspaceCreateNew}
|
checked={keyspaceCreateNew}
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
role="radio"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={(e) => handleOnChangeKeyspaceType(e, "Create new")}
|
onChange={() => {
|
||||||
|
setKeyspaceCreateNew(true);
|
||||||
|
setIsKeyspaceShared(false);
|
||||||
|
setExistingKeyspaceId("");
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="throughputInputRadioBtnLabel">Create new</span>
|
<span className="panelRadioBtnLabel">Create new</span>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
className="throughputInputRadioBtn"
|
className="panelRadioBtn"
|
||||||
aria-label="Use existing keyspace"
|
aria-label="Use existing keyspace"
|
||||||
checked={!keyspaceCreateNew}
|
checked={!keyspaceCreateNew}
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
role="radio"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={(e) => handleOnChangeKeyspaceType(e, "Use existing")}
|
onChange={() => {
|
||||||
|
setKeyspaceCreateNew(false);
|
||||||
|
setIsKeyspaceShared(false);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="throughputInputRadioBtnLabel">Use existing</span>
|
<span className="panelRadioBtnLabel">Use existing</span>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
{keyspaceCreateNew && (
|
||||||
|
<Stack className="panelGroupSpacing">
|
||||||
<TextField
|
<TextField
|
||||||
aria-required="true"
|
aria-required="true"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
styles={getTextFieldStyles()}
|
||||||
pattern="[^/?#\\]*[^/?# \\]"
|
pattern="[^/?#\\]*[^/?# \\]"
|
||||||
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
||||||
list={keyspaceCreateNew ? "" : "keyspacesList"}
|
placeholder="Type a new keyspace id"
|
||||||
placeholder={keyspaceCreateNew ? "Type a new keyspace id" : "Choose existing keyspace id"}
|
|
||||||
size={40}
|
size={40}
|
||||||
data-test="addCollection-keyspaceId"
|
value={newKeyspaceId}
|
||||||
value={keyspaceId}
|
onChange={(e, newValue) => setNewKeyspaceId(newValue)}
|
||||||
onChange={(e, newValue) => setKeyspaceId(newValue)}
|
|
||||||
ariaLabel="Keyspace id"
|
ariaLabel="Keyspace id"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<datalist id="keyspacesList">
|
|
||||||
{keyspaceIds?.map((id: string, index: number) => (
|
{!isServerlessAccount() && (
|
||||||
<option key={index}>{id}</option>
|
<Stack horizontal>
|
||||||
))}
|
<Checkbox
|
||||||
</datalist>
|
label="Provision shared throughput"
|
||||||
{canConfigureThroughput && keyspaceCreateNew && (
|
checked={isKeyspaceShared}
|
||||||
<div className="databaseProvision">
|
styles={{
|
||||||
<input
|
text: { fontSize: 12 },
|
||||||
tabIndex={0}
|
checkbox: { width: 12, height: 12 },
|
||||||
type="checkbox"
|
label: { padding: 0, alignItems: "center" },
|
||||||
id="keyspaceSharedThroughput"
|
}}
|
||||||
title="Provision shared throughput"
|
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) => setIsKeyspaceShared(isChecked)}
|
||||||
checked={keyspaceHasSharedOffer}
|
|
||||||
onChange={(e) => setKeyspaceHasSharedOffer(e.target.checked)}
|
|
||||||
/>
|
/>
|
||||||
<span className="databaseProvisionText" aria-label="Provision keyspace throughput">
|
|
||||||
Provision keyspace throughput
|
|
||||||
</span>
|
|
||||||
<InfoTooltip>
|
<InfoTooltip>
|
||||||
Provisioned throughput at the keyspace level will be shared across unlimited number of tables within the
|
Provisioned throughput at the keyspace level will be shared across unlimited number of tables within
|
||||||
keyspace
|
the keyspace
|
||||||
</InfoTooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
{canConfigureThroughput && keyspaceCreateNew && keyspaceHasSharedOffer && (
|
</Stack>
|
||||||
<div>
|
)}
|
||||||
|
|
||||||
|
{!keyspaceCreateNew && (
|
||||||
|
<Dropdown
|
||||||
|
ariaLabel="Choose existing keyspace id"
|
||||||
|
styles={{ root: { width: 300 }, title: { fontSize: 12 }, dropdownItem: { fontSize: 12 } }}
|
||||||
|
placeholder="Choose existing keyspace id"
|
||||||
|
defaultSelectedKey={existingKeyspaceId}
|
||||||
|
options={useDatabases.getState().databases?.map((keyspace) => ({
|
||||||
|
key: keyspace.id(),
|
||||||
|
text: keyspace.id(),
|
||||||
|
data: {
|
||||||
|
isShared: !!keyspace.offer(),
|
||||||
|
},
|
||||||
|
}))}
|
||||||
|
onChange={(event: React.FormEvent<HTMLDivElement>, option: IDropdownOption) => {
|
||||||
|
setExistingKeyspaceId(option.key as string);
|
||||||
|
setIsKeyspaceShared(option.data.isShared);
|
||||||
|
}}
|
||||||
|
responsiveMode={999}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isServerlessAccount() && keyspaceCreateNew && isKeyspaceShared && (
|
||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container.isFirstResourceCreated()}
|
showFreeTierExceedThroughputTooltip={
|
||||||
|
isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()
|
||||||
|
}
|
||||||
isDatabase
|
isDatabase
|
||||||
isSharded
|
isSharded
|
||||||
setThroughputValue={(throughput: number) => setKeyspaceThroughput(throughput)}
|
setThroughputValue={(throughput: number) => (newKeySpaceThroughput = throughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => setIsSharedAutoPilotSelected(isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (isNewKeySpaceAutoscale = isAutoscale)}
|
||||||
onCostAcknowledgeChange={(isAcknowledge: boolean) => {
|
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
||||||
setSharedThroughputSpendAck(isAcknowledge);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</Stack>
|
||||||
<div className="seconddivpadding">
|
|
||||||
<p>
|
<Stack>
|
||||||
<Label required>
|
<Stack horizontal>
|
||||||
Enter CQL command to create the table.
|
<span className="mandatoryStar">* </span>
|
||||||
<a href="https://aka.ms/cassandra-create-table" target="_blank" rel="noreferrer">
|
<Text className="panelTextBold" variant="small">
|
||||||
|
Enter CQL command to create the table.{" "}
|
||||||
|
<Link href="https://aka.ms/cassandra-create-table" target="_blank">
|
||||||
Learn More
|
Learn More
|
||||||
</a>
|
</Link>
|
||||||
</Label>
|
</Text>
|
||||||
</p>
|
</Stack>
|
||||||
<div aria-label={createTableQuery} style={{ float: "left", paddingTop: "3px", paddingRight: "3px" }}>
|
|
||||||
{createTableQuery}
|
<Stack horizontal verticalAlign="center">
|
||||||
</div>
|
<Text variant="small" style={{ marginRight: 4 }}>
|
||||||
|
{`CREATE TABLE ${keyspaceCreateNew ? newKeyspaceId : existingKeyspaceId}.`}
|
||||||
|
</Text>
|
||||||
<TextField
|
<TextField
|
||||||
|
underlined
|
||||||
|
styles={getTextFieldStyles({ fontSize: 12, width: 150 })}
|
||||||
aria-required="true"
|
aria-required="true"
|
||||||
ariaLabel="addCollection-tableId"
|
ariaLabel="addCollection-tableId"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
@@ -373,12 +291,13 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
||||||
placeholder="Enter table Id"
|
placeholder="Enter table Id"
|
||||||
size={20}
|
size={20}
|
||||||
className="textfontclr"
|
|
||||||
value={tableId}
|
value={tableId}
|
||||||
onChange={(e, newValue) => setTableId(newValue)}
|
onChange={(e, newValue) => setTableId(newValue)}
|
||||||
style={{ marginBottom: "5px" }}
|
|
||||||
/>
|
/>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
|
styles={getTextFieldStyles()}
|
||||||
multiline
|
multiline
|
||||||
id="editor-area"
|
id="editor-area"
|
||||||
rows={5}
|
rows={5}
|
||||||
@@ -386,10 +305,10 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
value={userTableQuery}
|
value={userTableQuery}
|
||||||
onChange={(e, newValue) => setUserTableQuery(newValue)}
|
onChange={(e, newValue) => setUserTableQuery(newValue)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Stack>
|
||||||
|
|
||||||
{canConfigureThroughput && keyspaceHasSharedOffer && !keyspaceCreateNew && (
|
{!isServerlessAccount() && isKeyspaceShared && !keyspaceCreateNew && (
|
||||||
<div className="seconddivpadding">
|
<Stack>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id="tableSharedThroughput"
|
id="tableSharedThroughput"
|
||||||
@@ -404,21 +323,17 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
does not count towards the throughput you provisioned for the keyspace. This throughput amount will be
|
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.
|
billed in addition to the throughput amount you provisioned at the keyspace level.
|
||||||
</InfoTooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
{canConfigureThroughput && (!keyspaceHasSharedOffer || dedicateTableThroughput) && (
|
{!isServerlessAccount() && (!isKeyspaceShared || dedicateTableThroughput) && (
|
||||||
<div>
|
|
||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container.isFirstResourceCreated()}
|
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
|
||||||
isDatabase={false}
|
isDatabase={false}
|
||||||
isSharded={false}
|
isSharded={false}
|
||||||
setThroughputValue={(throughput: number) => setThroughput(throughput)}
|
setThroughputValue={(throughput: number) => (tableThroughput = throughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => setIsAutoPilotSelected(isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (isTableAutoscale = isAutoscale)}
|
||||||
onCostAcknowledgeChange={(isAcknowledge: boolean) => {
|
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
||||||
setThroughputSpendAck(isAcknowledge);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</RightPaneForm>
|
</RightPaneForm>
|
||||||
|
|||||||
-163
@@ -1,163 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`CassandraAddCollectionPane Pane should render Default properly 1`] = `
|
|
||||||
<RightPaneForm
|
|
||||||
formError=""
|
|
||||||
onSubmit={[Function]}
|
|
||||||
submitButtonText="Apply"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="paneMainContent"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="seconddivpadding"
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
<StyledLabelBase
|
|
||||||
required={true}
|
|
||||||
>
|
|
||||||
Keyspace name
|
|
||||||
<InfoTooltip>
|
|
||||||
Select an existing keyspace or enter a new keyspace id.
|
|
||||||
</InfoTooltip>
|
|
||||||
</StyledLabelBase>
|
|
||||||
</p>
|
|
||||||
<Stack
|
|
||||||
horizontal={true}
|
|
||||||
verticalAlign="center"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
aria-label="Create new keyspace"
|
|
||||||
checked={true}
|
|
||||||
className="throughputInputRadioBtn"
|
|
||||||
onChange={[Function]}
|
|
||||||
role="radio"
|
|
||||||
tabIndex={0}
|
|
||||||
type="radio"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="throughputInputRadioBtnLabel"
|
|
||||||
>
|
|
||||||
Create new
|
|
||||||
</span>
|
|
||||||
<input
|
|
||||||
aria-label="Use existing keyspace"
|
|
||||||
checked={false}
|
|
||||||
className="throughputInputRadioBtn"
|
|
||||||
onChange={[Function]}
|
|
||||||
role="radio"
|
|
||||||
tabIndex={0}
|
|
||||||
type="radio"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="throughputInputRadioBtnLabel"
|
|
||||||
>
|
|
||||||
Use existing
|
|
||||||
</span>
|
|
||||||
</Stack>
|
|
||||||
<StyledTextFieldBase
|
|
||||||
aria-required="true"
|
|
||||||
ariaLabel="Keyspace id"
|
|
||||||
autoComplete="off"
|
|
||||||
autoFocus={true}
|
|
||||||
data-test="addCollection-keyspaceId"
|
|
||||||
list=""
|
|
||||||
onChange={[Function]}
|
|
||||||
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
|
|
||||||
placeholder="Type a new keyspace id"
|
|
||||||
size={40}
|
|
||||||
title="May not end with space nor contain characters '\\\\' '/' '#' '?'"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
<datalist
|
|
||||||
id="keyspacesList"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="databaseProvision"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
checked={false}
|
|
||||||
id="keyspaceSharedThroughput"
|
|
||||||
onChange={[Function]}
|
|
||||||
tabIndex={0}
|
|
||||||
title="Provision shared throughput"
|
|
||||||
type="checkbox"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
aria-label="Provision keyspace throughput"
|
|
||||||
className="databaseProvisionText"
|
|
||||||
>
|
|
||||||
Provision keyspace throughput
|
|
||||||
</span>
|
|
||||||
<InfoTooltip>
|
|
||||||
Provisioned throughput at the keyspace level will be shared across unlimited number of tables within the keyspace
|
|
||||||
</InfoTooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="seconddivpadding"
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
<StyledLabelBase
|
|
||||||
required={true}
|
|
||||||
>
|
|
||||||
Enter CQL command to create the table.
|
|
||||||
<a
|
|
||||||
href="https://aka.ms/cassandra-create-table"
|
|
||||||
rel="noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
Learn More
|
|
||||||
</a>
|
|
||||||
</StyledLabelBase>
|
|
||||||
</p>
|
|
||||||
<div
|
|
||||||
aria-label="CREATE TABLE "
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"float": "left",
|
|
||||||
"paddingRight": "3px",
|
|
||||||
"paddingTop": "3px",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
CREATE TABLE
|
|
||||||
</div>
|
|
||||||
<StyledTextFieldBase
|
|
||||||
aria-required="true"
|
|
||||||
ariaLabel="addCollection-tableId"
|
|
||||||
autoComplete="off"
|
|
||||||
className="textfontclr"
|
|
||||||
onChange={[Function]}
|
|
||||||
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
|
|
||||||
placeholder="Enter tableId"
|
|
||||||
size={20}
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"marginBottom": "5px",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
title="May not end with space nor contain characters '\\\\' '/' '#' '?'"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
<StyledTextFieldBase
|
|
||||||
aria-label="Table Schema"
|
|
||||||
id="editor-area"
|
|
||||||
multiline={true}
|
|
||||||
onChange={[Function]}
|
|
||||||
rows={5}
|
|
||||||
value="(userid int, name text, email text, PRIMARY KEY (userid))"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<ThroughputInput
|
|
||||||
isDatabase={false}
|
|
||||||
isSharded={false}
|
|
||||||
onCostAcknowledgeChange={[Function]}
|
|
||||||
setIsAutoscale={[Function]}
|
|
||||||
setThroughputValue={[Function]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</RightPaneForm>
|
|
||||||
`;
|
|
||||||
+55
-60
@@ -1,54 +1,53 @@
|
|||||||
jest.mock("../../../Common/dataAccess/deleteCollection");
|
jest.mock("../../../Common/dataAccess/deleteCollection");
|
||||||
jest.mock("../../../Shared/Telemetry/TelemetryProcessor");
|
jest.mock("../../../Shared/Telemetry/TelemetryProcessor");
|
||||||
import { mount, ReactWrapper, shallow } from "enzyme";
|
import { mount, shallow } from "enzyme";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { deleteCollection } from "../../../Common/dataAccess/deleteCollection";
|
import { deleteCollection } from "../../../Common/dataAccess/deleteCollection";
|
||||||
import DeleteFeedback from "../../../Common/DeleteFeedback";
|
import DeleteFeedback from "../../../Common/DeleteFeedback";
|
||||||
import { ApiKind, DatabaseAccount } from "../../../Contracts/DataModels";
|
import { ApiKind, DatabaseAccount } from "../../../Contracts/DataModels";
|
||||||
import { Collection, Database, TreeNode } from "../../../Contracts/ViewModels";
|
import { Collection, Database } from "../../../Contracts/ViewModels";
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { updateUserContext } from "../../../UserContext";
|
import { updateUserContext } from "../../../UserContext";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
import { useDatabases } from "../../useDatabases";
|
||||||
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
import { DeleteCollectionConfirmationPane } from "./DeleteCollectionConfirmationPane";
|
import { DeleteCollectionConfirmationPane } from "./DeleteCollectionConfirmationPane";
|
||||||
|
|
||||||
describe("Delete Collection Confirmation Pane", () => {
|
describe("Delete Collection Confirmation Pane", () => {
|
||||||
describe("Explorer.isLastCollection()", () => {
|
describe("useDatabases.isLastCollection()", () => {
|
||||||
let explorer: Explorer;
|
beforeAll(() => useDatabases.getState().clearDatabases());
|
||||||
|
afterEach(() => useDatabases.getState().clearDatabases());
|
||||||
beforeEach(() => {
|
|
||||||
explorer = new Explorer();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be true if 1 database and 1 collection", () => {
|
it("should be true if 1 database and 1 collection", () => {
|
||||||
const database = {} as Database;
|
const database = { id: ko.observable("testDB") } as Database;
|
||||||
database.collections = ko.observableArray<Collection>([{} as Collection]);
|
database.collections = ko.observableArray<Collection>([{ id: ko.observable("testCollection") } as Collection]);
|
||||||
explorer.databases = ko.observableArray<Database>([database]);
|
useDatabases.getState().addDatabases([database]);
|
||||||
expect(explorer.isLastCollection()).toBe(true);
|
expect(useDatabases.getState().isLastCollection()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be false if if 1 database and 2 collection", () => {
|
it("should be false if if 1 database and 2 collection", () => {
|
||||||
const database = {} as Database;
|
const database = { id: ko.observable("testDB") } as Database;
|
||||||
database.collections = ko.observableArray<Collection>([{} as Collection, {} as Collection]);
|
database.collections = ko.observableArray<Collection>([
|
||||||
explorer.databases = ko.observableArray<Database>([database]);
|
{ id: ko.observable("coll1") } as Collection,
|
||||||
expect(explorer.isLastCollection()).toBe(false);
|
{ id: ko.observable("coll2") } as Collection,
|
||||||
|
]);
|
||||||
|
useDatabases.getState().addDatabases([database]);
|
||||||
|
expect(useDatabases.getState().isLastCollection()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be false if 2 database and 1 collection each", () => {
|
it("should be false if 2 database and 1 collection each", () => {
|
||||||
const database = {} as Database;
|
const database = { id: ko.observable("testDB") } as Database;
|
||||||
database.collections = ko.observableArray<Collection>([{} as Collection]);
|
database.collections = ko.observableArray<Collection>([{ id: ko.observable("coll1") } as Collection]);
|
||||||
const database2 = {} as Database;
|
const database2 = { id: ko.observable("testDB2") } as Database;
|
||||||
database2.collections = ko.observableArray<Collection>([{} as Collection]);
|
database2.collections = ko.observableArray<Collection>([{ id: ko.observable("coll2") } as Collection]);
|
||||||
explorer.databases = ko.observableArray<Database>([database, database2]);
|
useDatabases.getState().addDatabases([database, database2]);
|
||||||
expect(explorer.isLastCollection()).toBe(false);
|
expect(useDatabases.getState().isLastCollection()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be false if 0 databases", () => {
|
it("should be false if 0 databases", () => {
|
||||||
const database = {} as Database;
|
expect(useDatabases.getState().isLastCollection()).toBe(false);
|
||||||
explorer.databases = ko.observableArray<Database>();
|
|
||||||
database.collections = ko.observableArray<Collection>();
|
|
||||||
expect(explorer.isLastCollection()).toBe(false);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -56,46 +55,39 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
it("should return true if last collection and database does not have shared throughput else false", () => {
|
it("should return true if last collection and database does not have shared throughput else false", () => {
|
||||||
const fakeExplorer = new Explorer();
|
const fakeExplorer = new Explorer();
|
||||||
fakeExplorer.refreshAllDatabases = () => undefined;
|
fakeExplorer.refreshAllDatabases = () => undefined;
|
||||||
fakeExplorer.isLastCollection = () => true;
|
|
||||||
fakeExplorer.isSelectedDatabaseShared = () => false;
|
|
||||||
|
|
||||||
const props = {
|
const wrapper = shallow(<DeleteCollectionConfirmationPane explorer={fakeExplorer} />);
|
||||||
explorer: fakeExplorer,
|
|
||||||
closePanel: (): void => undefined,
|
|
||||||
collectionName: "container",
|
|
||||||
};
|
|
||||||
const wrapper = shallow(<DeleteCollectionConfirmationPane {...props} />);
|
|
||||||
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(true);
|
|
||||||
|
|
||||||
props.explorer.isLastCollection = () => true;
|
|
||||||
props.explorer.isSelectedDatabaseShared = () => true;
|
|
||||||
wrapper.setProps(props);
|
|
||||||
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
|
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
|
||||||
|
|
||||||
props.explorer.isLastCollection = () => false;
|
const database = { id: ko.observable("testDB") } as Database;
|
||||||
props.explorer.isSelectedDatabaseShared = () => false;
|
database.collections = ko.observableArray<Collection>([{ id: ko.observable("testCollection") } as Collection]);
|
||||||
wrapper.setProps(props);
|
database.nodeKind = "Database";
|
||||||
|
database.isDatabaseShared = ko.computed(() => false);
|
||||||
|
useDatabases.getState().addDatabases([database]);
|
||||||
|
useSelectedNode.getState().setSelectedNode(database);
|
||||||
|
wrapper.setProps({ explorer: fakeExplorer });
|
||||||
|
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(true);
|
||||||
|
|
||||||
|
database.isDatabaseShared = ko.computed(() => true);
|
||||||
|
wrapper.setProps({ explorer: fakeExplorer });
|
||||||
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
|
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("submit()", () => {
|
describe("submit()", () => {
|
||||||
let wrapper: ReactWrapper;
|
|
||||||
const selectedCollectionId = "testCol";
|
const selectedCollectionId = "testCol";
|
||||||
const databaseId = "testDatabase";
|
const databaseId = "testDatabase";
|
||||||
const fakeExplorer = {} as Explorer;
|
const fakeExplorer = {} as Explorer;
|
||||||
fakeExplorer.findSelectedCollection = () => {
|
|
||||||
return {
|
|
||||||
id: ko.observable<string>(selectedCollectionId),
|
|
||||||
databaseId,
|
|
||||||
rid: "test",
|
|
||||||
} as Collection;
|
|
||||||
};
|
|
||||||
fakeExplorer.selectedCollectionId = ko.computed<string>(() => selectedCollectionId);
|
|
||||||
fakeExplorer.selectedNode = ko.observable<TreeNode>();
|
|
||||||
fakeExplorer.refreshAllDatabases = () => undefined;
|
fakeExplorer.refreshAllDatabases = () => undefined;
|
||||||
fakeExplorer.isLastCollection = () => true;
|
const database = { id: ko.observable(databaseId) } as Database;
|
||||||
fakeExplorer.isSelectedDatabaseShared = () => false;
|
const collection = {
|
||||||
|
id: ko.observable(selectedCollectionId),
|
||||||
|
nodeKind: "Collection",
|
||||||
|
database,
|
||||||
|
databaseId,
|
||||||
|
} as Collection;
|
||||||
|
database.collections = ko.observableArray<Collection>([collection]);
|
||||||
|
database.isDatabaseShared = ko.computed(() => false);
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
@@ -113,15 +105,17 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const props = {
|
useDatabases.getState().addDatabases([database]);
|
||||||
explorer: fakeExplorer,
|
useSelectedNode.getState().setSelectedNode(collection);
|
||||||
closePanel: (): void => undefined,
|
});
|
||||||
collectionName: "container",
|
|
||||||
};
|
afterEach(() => {
|
||||||
wrapper = mount(<DeleteCollectionConfirmationPane {...props} />);
|
useDatabases.getState().clearDatabases();
|
||||||
|
useSelectedNode.getState().setSelectedNode(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call delete collection", () => {
|
it("should call delete collection", () => {
|
||||||
|
const wrapper = mount(<DeleteCollectionConfirmationPane explorer={fakeExplorer} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
|
||||||
expect(wrapper.exists("#confirmCollectionId")).toBe(true);
|
expect(wrapper.exists("#confirmCollectionId")).toBe(true);
|
||||||
@@ -138,6 +132,7 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should record feedback", async () => {
|
it("should record feedback", async () => {
|
||||||
|
const wrapper = mount(<DeleteCollectionConfirmationPane explorer={fakeExplorer} />);
|
||||||
expect(wrapper.exists("#confirmCollectionId")).toBe(true);
|
expect(wrapper.exists("#confirmCollectionId")).toBe(true);
|
||||||
wrapper
|
wrapper
|
||||||
.find("#confirmCollectionId")
|
.find("#confirmCollectionId")
|
||||||
|
|||||||
+9
-5
@@ -13,7 +13,10 @@ import { userContext } from "../../../UserContext";
|
|||||||
import { getCollectionName } from "../../../Utils/APITypeUtils";
|
import { getCollectionName } from "../../../Utils/APITypeUtils";
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
import { useDatabases } from "../../useDatabases";
|
||||||
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
export interface DeleteCollectionConfirmationPaneProps {
|
export interface DeleteCollectionConfirmationPaneProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
}
|
}
|
||||||
@@ -27,13 +30,14 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
const [isExecuting, setIsExecuting] = useState(false);
|
const [isExecuting, setIsExecuting] = useState(false);
|
||||||
|
|
||||||
const shouldRecordFeedback = (): boolean => {
|
const shouldRecordFeedback = (): boolean =>
|
||||||
return explorer.isLastCollection() && !explorer.isSelectedDatabaseShared();
|
useDatabases.getState().isLastCollection() &&
|
||||||
};
|
!useSelectedNode.getState().findSelectedDatabase()?.isDatabaseShared();
|
||||||
|
|
||||||
const collectionName = getCollectionName().toLocaleLowerCase();
|
const collectionName = getCollectionName().toLocaleLowerCase();
|
||||||
const paneTitle = "Delete " + collectionName;
|
const paneTitle = "Delete " + collectionName;
|
||||||
const onSubmit = async (): Promise<void> => {
|
const onSubmit = async (): Promise<void> => {
|
||||||
const collection = explorer.findSelectedCollection();
|
const collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
if (!collection || inputCollectionName !== collection.id()) {
|
if (!collection || inputCollectionName !== collection.id()) {
|
||||||
const errorMessage = "Input " + collectionName + " name does not match the selected " + collectionName;
|
const errorMessage = "Input " + collectionName + " name does not match the selected " + collectionName;
|
||||||
setFormError(errorMessage);
|
setFormError(errorMessage);
|
||||||
@@ -58,7 +62,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
await deleteCollection(collection.databaseId, collection.id());
|
await deleteCollection(collection.databaseId, collection.id());
|
||||||
|
|
||||||
setIsExecuting(false);
|
setIsExecuting(false);
|
||||||
explorer.selectedNode(collection.database);
|
useSelectedNode.getState().setSelectedNode(collection.database);
|
||||||
explorer.tabsManager?.closeTabsByComparator(
|
explorer.tabsManager?.closeTabsByComparator(
|
||||||
(tab) => tab.node?.id() === collection.id() && (tab.node as Collection).databaseId === collection.databaseId
|
(tab) => tab.node?.id() === collection.id() && (tab.node as Collection).databaseId === collection.databaseId
|
||||||
);
|
);
|
||||||
|
|||||||
+10
-366
@@ -2,16 +2,9 @@
|
|||||||
|
|
||||||
exports[`Delete Collection Confirmation Pane submit() should call delete collection 1`] = `
|
exports[`Delete Collection Confirmation Pane submit() should call delete collection 1`] = `
|
||||||
<DeleteCollectionConfirmationPane
|
<DeleteCollectionConfirmationPane
|
||||||
closePanel={[Function]}
|
|
||||||
collectionName="container"
|
|
||||||
explorer={
|
explorer={
|
||||||
Object {
|
Object {
|
||||||
"findSelectedCollection": [Function],
|
|
||||||
"isLastCollection": [Function],
|
|
||||||
"isSelectedDatabaseShared": [Function],
|
|
||||||
"refreshAllDatabases": [Function],
|
"refreshAllDatabases": [Function],
|
||||||
"selectedCollectionId": [Function],
|
|
||||||
"selectedNode": [Function],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -43,7 +36,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="css-102"
|
className="css-53"
|
||||||
>
|
>
|
||||||
Confirm by typing the
|
Confirm by typing the
|
||||||
container
|
container
|
||||||
@@ -347,18 +340,18 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
value=""
|
value=""
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField root-104"
|
className="ms-TextField root-55"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-wrapper"
|
className="ms-TextField-wrapper"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-fieldGroup fieldGroup-105"
|
className="ms-TextField-fieldGroup fieldGroup-56"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className="ms-TextField-field field-106"
|
className="ms-TextField-field field-57"
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -373,355 +366,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
</TextFieldBase>
|
</TextFieldBase>
|
||||||
</StyledTextFieldBase>
|
</StyledTextFieldBase>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
className="deleteCollectionFeedback"
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
block={true}
|
|
||||||
variant="small"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="css-115"
|
|
||||||
>
|
|
||||||
Help us improve Azure Cosmos DB!
|
|
||||||
</span>
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
block={true}
|
|
||||||
variant="small"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="css-115"
|
|
||||||
>
|
|
||||||
What is the reason why you are deleting this
|
|
||||||
container
|
|
||||||
?
|
|
||||||
</span>
|
|
||||||
</Text>
|
|
||||||
<StyledTextFieldBase
|
|
||||||
id="deleteCollectionFeedbackInput"
|
|
||||||
multiline={true}
|
|
||||||
onChange={[Function]}
|
|
||||||
rows={3}
|
|
||||||
styles={
|
|
||||||
Object {
|
|
||||||
"fieldGroup": Object {
|
|
||||||
"width": 300,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
value=""
|
|
||||||
>
|
|
||||||
<TextFieldBase
|
|
||||||
deferredValidationTime={200}
|
|
||||||
id="deleteCollectionFeedbackInput"
|
|
||||||
multiline={true}
|
|
||||||
onChange={[Function]}
|
|
||||||
resizable={true}
|
|
||||||
rows={3}
|
|
||||||
styles={[Function]}
|
|
||||||
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",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
validateOnLoad={true}
|
|
||||||
value=""
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="ms-TextField ms-TextField--multiline root-104"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="ms-TextField-wrapper"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="ms-TextField-fieldGroup fieldGroup-116"
|
|
||||||
>
|
|
||||||
<textarea
|
|
||||||
aria-invalid={false}
|
|
||||||
className="ms-TextField-field field-117"
|
|
||||||
id="deleteCollectionFeedbackInput"
|
|
||||||
onBlur={[Function]}
|
|
||||||
onChange={[Function]}
|
|
||||||
onFocus={[Function]}
|
|
||||||
onInput={[Function]}
|
|
||||||
rows={3}
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TextFieldBase>
|
|
||||||
</StyledTextFieldBase>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PanelFooterComponent
|
<PanelFooterComponent
|
||||||
@@ -2434,7 +2078,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
aria-label="OK"
|
aria-label="OK"
|
||||||
className="ms-Button ms-Button--primary root-119"
|
className="ms-Button ms-Button--primary root-66"
|
||||||
data-is-focusable={true}
|
data-is-focusable={true}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
@@ -2446,16 +2090,16 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-flexContainer flexContainer-120"
|
className="ms-Button-flexContainer flexContainer-67"
|
||||||
data-automationid="splitbuttonprimary"
|
data-automationid="splitbuttonprimary"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-textContainer textContainer-121"
|
className="ms-Button-textContainer textContainer-68"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-label label-123"
|
className="ms-Button-label label-70"
|
||||||
id="id__6"
|
id="id__3"
|
||||||
key="id__6"
|
key="id__3"
|
||||||
>
|
>
|
||||||
OK
|
OK
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
jest.mock("../../Common/dataAccess/deleteDatabase");
|
jest.mock("../../Common/dataAccess/deleteDatabase");
|
||||||
jest.mock("../../Shared/Telemetry/TelemetryProcessor");
|
jest.mock("../../Shared/Telemetry/TelemetryProcessor");
|
||||||
import { mount, ReactWrapper, shallow } from "enzyme";
|
import { mount, shallow } from "enzyme";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
|
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
|
||||||
@@ -11,53 +11,16 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
|
|||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
|
import { TabsManager } from "../Tabs/TabsManager";
|
||||||
|
import { useDatabases } from "../useDatabases";
|
||||||
|
import { useSelectedNode } from "../useSelectedNode";
|
||||||
import { DeleteDatabaseConfirmationPanel } from "./DeleteDatabaseConfirmationPanel";
|
import { DeleteDatabaseConfirmationPanel } from "./DeleteDatabaseConfirmationPanel";
|
||||||
|
|
||||||
describe("Delete Database Confirmation Pane", () => {
|
describe("Delete Database Confirmation Pane", () => {
|
||||||
describe("shouldRecordFeedback()", () => {
|
const selectedDatabaseId = "testDatabase";
|
||||||
it("should return true if last non empty database or is last database that has shared throughput, else false", () => {
|
let fakeExplorer: Explorer;
|
||||||
const fakeExplorer = new Explorer();
|
let database: Database;
|
||||||
fakeExplorer.refreshAllDatabases = () => undefined;
|
|
||||||
fakeExplorer.isLastCollection = () => true;
|
|
||||||
fakeExplorer.isSelectedDatabaseShared = () => false;
|
|
||||||
|
|
||||||
const database = {} as Database;
|
|
||||||
database.collections = ko.observableArray<Collection>([{} as Collection]);
|
|
||||||
database.id = ko.observable<string>("testDatabse");
|
|
||||||
|
|
||||||
const props = {
|
|
||||||
explorer: fakeExplorer,
|
|
||||||
closePanel: (): void => undefined,
|
|
||||||
openNotificationConsole: (): void => undefined,
|
|
||||||
selectedDatabase: database,
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapper = shallow(<DeleteDatabaseConfirmationPanel {...props} />);
|
|
||||||
props.explorer.isLastNonEmptyDatabase = () => true;
|
|
||||||
wrapper.setProps(props);
|
|
||||||
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(true);
|
|
||||||
|
|
||||||
props.explorer.isLastNonEmptyDatabase = () => false;
|
|
||||||
props.explorer.isLastDatabase = () => false;
|
|
||||||
wrapper.setProps(props);
|
|
||||||
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(false);
|
|
||||||
|
|
||||||
props.explorer.isLastNonEmptyDatabase = () => false;
|
|
||||||
props.explorer.isLastDatabase = () => true;
|
|
||||||
props.explorer.isSelectedDatabaseShared = () => false;
|
|
||||||
wrapper.setProps(props);
|
|
||||||
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("submit()", () => {
|
|
||||||
const selectedDatabaseId = "testDatabse";
|
|
||||||
const fakeExplorer = new Explorer();
|
|
||||||
fakeExplorer.refreshAllDatabases = () => undefined;
|
|
||||||
fakeExplorer.isLastCollection = () => true;
|
|
||||||
fakeExplorer.isSelectedDatabaseShared = () => false;
|
|
||||||
|
|
||||||
let wrapper: ReactWrapper;
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
@@ -74,23 +37,36 @@ describe("Delete Database Confirmation Pane", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const database = {} as Database;
|
fakeExplorer = {} as Explorer;
|
||||||
database.collections = ko.observableArray<Collection>([{} as Collection]);
|
fakeExplorer.refreshAllDatabases = () => undefined;
|
||||||
|
fakeExplorer.tabsManager = new TabsManager();
|
||||||
|
|
||||||
|
database = {} as Database;
|
||||||
|
database.collections = ko.observableArray<Collection>([{ id: ko.observable("testCollection") } as Collection]);
|
||||||
database.id = ko.observable<string>(selectedDatabaseId);
|
database.id = ko.observable<string>(selectedDatabaseId);
|
||||||
|
database.nodeKind = "Database";
|
||||||
|
|
||||||
const props = {
|
useDatabases.getState().addDatabases([database]);
|
||||||
explorer: fakeExplorer,
|
useSelectedNode.getState().setSelectedNode(database);
|
||||||
closePanel: (): void => undefined,
|
});
|
||||||
openNotificationConsole: (): void => undefined,
|
|
||||||
selectedDatabase: database,
|
|
||||||
};
|
|
||||||
|
|
||||||
wrapper = mount(<DeleteDatabaseConfirmationPanel {...props} />);
|
afterEach(() => {
|
||||||
props.explorer.isLastNonEmptyDatabase = () => true;
|
useDatabases.getState().clearDatabases();
|
||||||
wrapper.setProps(props);
|
useSelectedNode.getState().setSelectedNode(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shouldRecordFeedback() should return true if last non empty database or is last database that has shared throughput", () => {
|
||||||
|
const wrapper = shallow(<DeleteDatabaseConfirmationPanel explorer={fakeExplorer} />);
|
||||||
|
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(true);
|
||||||
|
|
||||||
|
useDatabases.getState().addDatabases([database]);
|
||||||
|
wrapper.setProps({ explorer: fakeExplorer });
|
||||||
|
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(false);
|
||||||
|
useDatabases.getState().clearDatabases();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should call delete database", () => {
|
it("Should call delete database", () => {
|
||||||
|
const wrapper = mount(<DeleteDatabaseConfirmationPanel explorer={fakeExplorer} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
||||||
|
|
||||||
@@ -105,6 +81,7 @@ describe("Delete Database Confirmation Pane", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should record feedback", async () => {
|
it("should record feedback", async () => {
|
||||||
|
const wrapper = mount(<DeleteDatabaseConfirmationPanel explorer={fakeExplorer} />);
|
||||||
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
||||||
wrapper
|
wrapper
|
||||||
.find("#confirmDatabaseId")
|
.find("#confirmDatabaseId")
|
||||||
@@ -135,4 +112,3 @@ describe("Delete Database Confirmation Pane", () => {
|
|||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|||||||
@@ -13,24 +13,26 @@ import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
|||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
|
import { useDatabases } from "../useDatabases";
|
||||||
|
import { useSelectedNode } from "../useSelectedNode";
|
||||||
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
|
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "./RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "./RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
interface DeleteDatabaseConfirmationPanelProps {
|
interface DeleteDatabaseConfirmationPanelProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
selectedDatabase: Database;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseConfirmationPanelProps> = ({
|
export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseConfirmationPanelProps> = ({
|
||||||
explorer,
|
explorer,
|
||||||
selectedDatabase,
|
|
||||||
}: DeleteDatabaseConfirmationPanelProps): JSX.Element => {
|
}: DeleteDatabaseConfirmationPanelProps): JSX.Element => {
|
||||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
|
const isLastNonEmptyDatabase = useDatabases((state) => state.isLastNonEmptyDatabase);
|
||||||
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
||||||
|
|
||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
const [databaseInput, setDatabaseInput] = useState<string>("");
|
const [databaseInput, setDatabaseInput] = useState<string>("");
|
||||||
const [databaseFeedbackInput, setDatabaseFeedbackInput] = useState<string>("");
|
const [databaseFeedbackInput, setDatabaseFeedbackInput] = useState<string>("");
|
||||||
|
const selectedDatabase: Database = useSelectedNode.getState().findSelectedDatabase();
|
||||||
|
|
||||||
const submit = async (): Promise<void> => {
|
const submit = async (): Promise<void> => {
|
||||||
if (selectedDatabase?.id() && databaseInput !== selectedDatabase.id()) {
|
if (selectedDatabase?.id() && databaseInput !== selectedDatabase.id()) {
|
||||||
@@ -52,7 +54,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
closeSidePanel();
|
closeSidePanel();
|
||||||
explorer.refreshAllDatabases();
|
explorer.refreshAllDatabases();
|
||||||
explorer.tabsManager.closeTabsByComparator((tab) => tab.node?.id() === selectedDatabase.id());
|
explorer.tabsManager.closeTabsByComparator((tab) => tab.node?.id() === selectedDatabase.id());
|
||||||
explorer.selectedNode(undefined);
|
useSelectedNode.getState().setSelectedNode(undefined);
|
||||||
selectedDatabase
|
selectedDatabase
|
||||||
.collections()
|
.collections()
|
||||||
.forEach((collection: Collection) =>
|
.forEach((collection: Collection) =>
|
||||||
@@ -70,7 +72,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
|
|
||||||
if (shouldRecordFeedback()) {
|
if (isLastNonEmptyDatabase()) {
|
||||||
const deleteFeedback = new DeleteFeedback(
|
const deleteFeedback = new DeleteFeedback(
|
||||||
userContext?.databaseAccount.id,
|
userContext?.databaseAccount.id,
|
||||||
userContext?.databaseAccount.name,
|
userContext?.databaseAccount.name,
|
||||||
@@ -100,10 +102,6 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const shouldRecordFeedback = (): boolean => {
|
|
||||||
return explorer.isLastNonEmptyDatabase() || (explorer.isLastDatabase() && explorer.isSelectedDatabaseShared());
|
|
||||||
};
|
|
||||||
|
|
||||||
const props: RightPaneFormProps = {
|
const props: RightPaneFormProps = {
|
||||||
formError,
|
formError,
|
||||||
isExecuting: isLoading,
|
isExecuting: isLoading,
|
||||||
@@ -134,7 +132,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{shouldRecordFeedback() && (
|
{isLastNonEmptyDatabase() && (
|
||||||
<div className="deleteDatabaseFeedback">
|
<div className="deleteDatabaseFeedback">
|
||||||
<Text variant="small" block>
|
<Text variant="small" block>
|
||||||
Help us improve Azure Cosmos DB!
|
Help us improve Azure Cosmos DB!
|
||||||
|
|||||||
@@ -19,17 +19,11 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
|||||||
"container": Explorer {
|
"container": Explorer {
|
||||||
"_isInitializingNotebooks": false,
|
"_isInitializingNotebooks": false,
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"canSaveQueries": [Function],
|
|
||||||
"collapsedResourceTreeWidth": 36,
|
|
||||||
"databases": [Function],
|
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
"isHostedDataExplorerEnabled": [Function],
|
|
||||||
"isNotebookEnabled": [Function],
|
"isNotebookEnabled": [Function],
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
"isNotebooksEnabledForAccount": [Function],
|
||||||
"isResourceTokenCollectionNodeSelected": [Function],
|
|
||||||
"isSchemaEnabled": [Function],
|
"isSchemaEnabled": [Function],
|
||||||
"isServerlessEnabled": [Function],
|
|
||||||
"isShellEnabled": [Function],
|
"isShellEnabled": [Function],
|
||||||
"isSynapseLinkUpdating": [Function],
|
"isSynapseLinkUpdating": [Function],
|
||||||
"isTabsContentExpanded": [Function],
|
"isTabsContentExpanded": [Function],
|
||||||
@@ -44,38 +38,16 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
|||||||
},
|
},
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"resourceTokenCollection": [Function],
|
"resourceTokenCollection": [Function],
|
||||||
"resourceTokenCollectionId": [Function],
|
|
||||||
"resourceTokenDatabaseId": [Function],
|
|
||||||
"resourceTokenPartitionKey": [Function],
|
|
||||||
"resourceTree": ResourceTreeAdapter {
|
"resourceTree": ResourceTreeAdapter {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"copyNotebook": [Function],
|
"copyNotebook": [Function],
|
||||||
"databaseCollectionIdMap": Map {},
|
|
||||||
"koSubsCollectionIdMap": Map {},
|
|
||||||
"koSubsDatabaseIdMap": Map {},
|
|
||||||
"parameters": [Function],
|
"parameters": [Function],
|
||||||
},
|
},
|
||||||
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"parameters": [Function],
|
"parameters": [Function],
|
||||||
},
|
},
|
||||||
"selectedDatabaseId": [Function],
|
|
||||||
"selectedNode": [Function],
|
|
||||||
"setInProgressConsoleDataIdToBeDeleted": undefined,
|
|
||||||
"setNotificationConsoleData": undefined,
|
|
||||||
"sparkClusterConnectionInfo": [Function],
|
"sparkClusterConnectionInfo": [Function],
|
||||||
"splitter": Splitter {
|
|
||||||
"bounds": Object {
|
|
||||||
"max": 400,
|
|
||||||
"min": 240,
|
|
||||||
},
|
|
||||||
"direction": "vertical",
|
|
||||||
"isCollapsed": [Function],
|
|
||||||
"leftSideId": "resourcetree",
|
|
||||||
"onResizeStart": [Function],
|
|
||||||
"onResizeStop": [Function],
|
|
||||||
"splitterId": "h_splitter1",
|
|
||||||
},
|
|
||||||
"tabsManager": TabsManager {
|
"tabsManager": TabsManager {
|
||||||
"activeTab": [Function],
|
"activeTab": [Function],
|
||||||
"openedTabs": [Function],
|
"openedTabs": [Function],
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ export const GraphStylingPanel: FunctionComponent<GraphStylingProps> = ({
|
|||||||
|
|
||||||
const buttonLabel = "Ok";
|
const buttonLabel = "Ok";
|
||||||
|
|
||||||
const submit = () => {
|
const submit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
closeSidePanel();
|
closeSidePanel();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,10 @@
|
|||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Explorer from "../../Explorer";
|
|
||||||
import { LoadQueryPane } from "./LoadQueryPane";
|
import { LoadQueryPane } from "./LoadQueryPane";
|
||||||
|
|
||||||
describe("Load Query Pane", () => {
|
describe("Load Query Pane", () => {
|
||||||
it("should render Default properly", () => {
|
it("should render Default properly", () => {
|
||||||
const fakeExplorer = {} as Explorer;
|
const wrapper = shallow(<LoadQueryPane />);
|
||||||
const props = {
|
|
||||||
explorer: fakeExplorer,
|
|
||||||
closePanel: (): void => undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapper = shallow(<LoadQueryPane {...props} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,15 +7,10 @@ import { Collection } from "../../../Contracts/ViewModels";
|
|||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
|
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
import QueryTab from "../../Tabs/QueryTab";
|
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
interface LoadQueryPaneProps {
|
export const LoadQueryPane: FunctionComponent = (): JSX.Element => {
|
||||||
explorer: Explorer;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LoadQueryPane: FunctionComponent<LoadQueryPaneProps> = ({ explorer }: LoadQueryPaneProps): JSX.Element => {
|
|
||||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
@@ -59,21 +54,20 @@ export const LoadQueryPane: FunctionComponent<LoadQueryPaneProps> = ({ explorer
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loadQueryFromFile = async (file: File): Promise<void> => {
|
const loadQueryFromFile = async (file: File): Promise<void> => {
|
||||||
const selectedCollection: Collection = explorer?.findSelectedCollection();
|
const selectedCollection: Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
|
const reader = new FileReader();
|
||||||
|
let fileData: string;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
reader.onload = (evt: any): void => {
|
||||||
|
fileData = evt.target.result;
|
||||||
|
|
||||||
if (!selectedCollection) {
|
if (!selectedCollection) {
|
||||||
logError("No collection was selected", "LoadQueryPane.loadQueryFromFile");
|
logError("No collection was selected", "LoadQueryPane.loadQueryFromFile");
|
||||||
} else if (userContext.apiType === "Mongo") {
|
} else if (userContext.apiType === "Mongo") {
|
||||||
selectedCollection.onNewMongoQueryClick(selectedCollection, undefined);
|
selectedCollection.onNewMongoQueryClick(selectedCollection, undefined);
|
||||||
} else {
|
} else {
|
||||||
selectedCollection.onNewQueryClick(selectedCollection, undefined);
|
selectedCollection.onNewQueryClick(selectedCollection, undefined, fileData);
|
||||||
}
|
}
|
||||||
const reader = new FileReader();
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
reader.onload = (evt: any): void => {
|
|
||||||
const fileData: string = evt.target.result;
|
|
||||||
const queryTab = explorer.tabsManager.activeTab() as QueryTab;
|
|
||||||
queryTab.initialEditorContent(fileData);
|
|
||||||
queryTab.sqlQueryEditorContent(fileData);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
reader.onerror = (): void => {
|
reader.onerror = (): void => {
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { ITextFieldStyles } from "@fluentui/react";
|
||||||
|
|
||||||
|
interface TextFieldStylesProps {
|
||||||
|
fontSize: number | string;
|
||||||
|
width: number | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getTextFieldStyles = (params?: TextFieldStylesProps): Partial<ITextFieldStyles> => ({
|
||||||
|
field: {
|
||||||
|
fontSize: params?.fontSize || 12,
|
||||||
|
selectors: {
|
||||||
|
"::placeholder": {
|
||||||
|
fontSize: params?.fontSize || 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
root: {
|
||||||
|
width: params?.width || 300,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,32 +1,38 @@
|
|||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { SavedQueries } from "../../../Common/Constants";
|
||||||
|
import { Collection, Database } from "../../../Contracts/ViewModels";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { SaveQueryPane } from "./SaveQueryPane";
|
import { SaveQueryPane } from "./SaveQueryPane";
|
||||||
|
|
||||||
describe("Save Query Pane", () => {
|
describe("Save Query Pane", () => {
|
||||||
const fakeExplorer = {} as Explorer;
|
const fakeExplorer = {} as Explorer;
|
||||||
fakeExplorer.canSaveQueries = ko.computed<boolean>(() => true);
|
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
explorer: fakeExplorer,
|
explorer: fakeExplorer,
|
||||||
closePanel: (): void => undefined,
|
closePanel: (): void => undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<SaveQueryPane {...props} />);
|
|
||||||
|
|
||||||
it("should return true if can save Queries else false", () => {
|
|
||||||
fakeExplorer.canSaveQueries = ko.computed<boolean>(() => true);
|
|
||||||
wrapper.setProps(props);
|
|
||||||
expect(wrapper.exists("#saveQueryInput")).toBe(true);
|
|
||||||
|
|
||||||
fakeExplorer.canSaveQueries = ko.computed<boolean>(() => false);
|
|
||||||
wrapper.setProps(props);
|
|
||||||
expect(wrapper.exists("#saveQueryInput")).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render Default properly", () => {
|
it("should render Default properly", () => {
|
||||||
const wrapper = shallow(<SaveQueryPane {...props} />);
|
const wrapper = shallow(<SaveQueryPane {...props} />);
|
||||||
|
expect(wrapper.exists("#saveQueryInput")).toBe(false);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should return true if can save Queries else false", () => {
|
||||||
|
useDatabases.getState().addDatabases([
|
||||||
|
{
|
||||||
|
id: ko.observable(SavedQueries.DatabaseName),
|
||||||
|
collections: ko.observableArray([
|
||||||
|
{
|
||||||
|
id: ko.observable(SavedQueries.CollectionName),
|
||||||
|
} as Collection,
|
||||||
|
]),
|
||||||
|
} as Database,
|
||||||
|
]);
|
||||||
|
const wrapper = shallow(<SaveQueryPane {...props} />);
|
||||||
|
expect(wrapper.exists("#saveQueryInput")).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
|||||||
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import QueryTab from "../../Tabs/QueryTab";
|
import { NewQueryTab } from "../../Tabs/QueryTab/QueryTab";
|
||||||
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
interface SaveQueryPaneProps {
|
interface SaveQueryPaneProps {
|
||||||
@@ -24,17 +25,18 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({ explorer
|
|||||||
|
|
||||||
const setupSaveQueriesText = `For compliance reasons, we save queries in a container in your Azure Cosmos account, in a separate database called “${SavedQueries.DatabaseName}”. To proceed, we need to create a container in your account, estimated additional cost is $0.77 daily.`;
|
const setupSaveQueriesText = `For compliance reasons, we save queries in a container in your Azure Cosmos account, in a separate database called “${SavedQueries.DatabaseName}”. To proceed, we need to create a container in your account, estimated additional cost is $0.77 daily.`;
|
||||||
const title = "Save Query";
|
const title = "Save Query";
|
||||||
const { canSaveQueries } = explorer;
|
const isSaveQueryEnabled = useDatabases((state) => state.isSaveQueryEnabled);
|
||||||
|
|
||||||
const submit = async (): Promise<void> => {
|
const submit = async (): Promise<void> => {
|
||||||
setFormError("");
|
setFormError("");
|
||||||
if (!canSaveQueries()) {
|
if (!isSaveQueryEnabled()) {
|
||||||
setFormError("Cannot save query");
|
setFormError("Cannot save query");
|
||||||
logConsoleError("Failed to save query: account not setup to save queries");
|
logConsoleError("Failed to save query: account not setup to save queries");
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryTab = explorer && (explorer.tabsManager.activeTab() as QueryTab);
|
const queryTab = explorer && (explorer.tabsManager.activeTab() as NewQueryTab);
|
||||||
const query: string = queryTab && queryTab.sqlQueryEditorContent();
|
const query: string = queryTab && queryTab.iTabAccessor.onSaveClickEvent();
|
||||||
|
|
||||||
if (!queryName || queryName.length === 0) {
|
if (!queryName || queryName.length === 0) {
|
||||||
setFormError("No query name specified");
|
setFormError("No query name specified");
|
||||||
logConsoleError("Could not save query -- No query name specified. Please specify a query name.");
|
logConsoleError("Could not save query -- No query name specified. Please specify a query name.");
|
||||||
@@ -128,16 +130,16 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({ explorer
|
|||||||
const props: RightPaneFormProps = {
|
const props: RightPaneFormProps = {
|
||||||
formError: formError,
|
formError: formError,
|
||||||
isExecuting: isLoading,
|
isExecuting: isLoading,
|
||||||
submitButtonText: canSaveQueries() ? "Save" : "Complete setup",
|
submitButtonText: isSaveQueryEnabled() ? "Save" : "Complete setup",
|
||||||
onSubmit: () => {
|
onSubmit: () => {
|
||||||
canSaveQueries() ? submit() : setupQueries();
|
isSaveQueryEnabled() ? submit() : setupQueries();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<RightPaneForm {...props}>
|
||||||
<div className="panelFormWrapper">
|
<div className="panelFormWrapper">
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
{!canSaveQueries() ? (
|
{!isSaveQueryEnabled() ? (
|
||||||
<Text variant="small">{setupSaveQueriesText}</Text>
|
<Text variant="small">{setupSaveQueriesText}</Text>
|
||||||
) : (
|
) : (
|
||||||
<TextField
|
<TextField
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useSidePanel } from "../../../hooks/useSidePanel";
|
|||||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
|
import { createOrUpdate } from "../../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
|
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
|
||||||
@@ -56,8 +57,10 @@ export const SetupNoteBooksPanel: FunctionComponent<SetupNoteBooksPanelProps> =
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setLoadingTrue();
|
setLoadingTrue();
|
||||||
await explorer.notebookWorkspaceManager.createNotebookWorkspaceAsync(
|
await createOrUpdate(
|
||||||
userContext.databaseAccount && userContext.databaseAccount.id,
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
"default"
|
"default"
|
||||||
);
|
);
|
||||||
explorer.isAccountReady.valueHasMutated(); // re-trigger init notebooks
|
explorer.isAccountReady.valueHasMutated(); // re-trigger init notebooks
|
||||||
|
|||||||
@@ -9,17 +9,11 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
Explorer {
|
Explorer {
|
||||||
"_isInitializingNotebooks": false,
|
"_isInitializingNotebooks": false,
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"canSaveQueries": [Function],
|
|
||||||
"collapsedResourceTreeWidth": 36,
|
|
||||||
"databases": [Function],
|
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
"isHostedDataExplorerEnabled": [Function],
|
|
||||||
"isNotebookEnabled": [Function],
|
"isNotebookEnabled": [Function],
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
"isNotebooksEnabledForAccount": [Function],
|
||||||
"isResourceTokenCollectionNodeSelected": [Function],
|
|
||||||
"isSchemaEnabled": [Function],
|
"isSchemaEnabled": [Function],
|
||||||
"isServerlessEnabled": [Function],
|
|
||||||
"isShellEnabled": [Function],
|
"isShellEnabled": [Function],
|
||||||
"isSynapseLinkUpdating": [Function],
|
"isSynapseLinkUpdating": [Function],
|
||||||
"isTabsContentExpanded": [Function],
|
"isTabsContentExpanded": [Function],
|
||||||
@@ -34,38 +28,16 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
},
|
},
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"resourceTokenCollection": [Function],
|
"resourceTokenCollection": [Function],
|
||||||
"resourceTokenCollectionId": [Function],
|
|
||||||
"resourceTokenDatabaseId": [Function],
|
|
||||||
"resourceTokenPartitionKey": [Function],
|
|
||||||
"resourceTree": ResourceTreeAdapter {
|
"resourceTree": ResourceTreeAdapter {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"copyNotebook": [Function],
|
"copyNotebook": [Function],
|
||||||
"databaseCollectionIdMap": Map {},
|
|
||||||
"koSubsCollectionIdMap": Map {},
|
|
||||||
"koSubsDatabaseIdMap": Map {},
|
|
||||||
"parameters": [Function],
|
"parameters": [Function],
|
||||||
},
|
},
|
||||||
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"parameters": [Function],
|
"parameters": [Function],
|
||||||
},
|
},
|
||||||
"selectedDatabaseId": [Function],
|
|
||||||
"selectedNode": [Function],
|
|
||||||
"setInProgressConsoleDataIdToBeDeleted": undefined,
|
|
||||||
"setNotificationConsoleData": undefined,
|
|
||||||
"sparkClusterConnectionInfo": [Function],
|
"sparkClusterConnectionInfo": [Function],
|
||||||
"splitter": Splitter {
|
|
||||||
"bounds": Object {
|
|
||||||
"max": 400,
|
|
||||||
"min": 240,
|
|
||||||
},
|
|
||||||
"direction": "vertical",
|
|
||||||
"isCollapsed": [Function],
|
|
||||||
"leftSideId": "resourcetree",
|
|
||||||
"onResizeStart": [Function],
|
|
||||||
"onResizeStop": [Function],
|
|
||||||
"splitterId": "h_splitter1",
|
|
||||||
},
|
|
||||||
"tabsManager": TabsManager {
|
"tabsManager": TabsManager {
|
||||||
"activeTab": [Function],
|
"activeTab": [Function],
|
||||||
"openedTabs": [Function],
|
"openedTabs": [Function],
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Explorer from "../../Explorer";
|
|
||||||
import { UploadItemsPane } from "./UploadItemsPane";
|
import { UploadItemsPane } from "./UploadItemsPane";
|
||||||
const props = {
|
|
||||||
explorer: new Explorer(),
|
|
||||||
};
|
|
||||||
describe("Upload Items Pane", () => {
|
describe("Upload Items Pane", () => {
|
||||||
it("should render Default properly", () => {
|
it("should render Default properly", () => {
|
||||||
const wrapper = shallow(<UploadItemsPane {...props} />);
|
const wrapper = shallow(<UploadItemsPane />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,15 +3,11 @@ import React, { ChangeEvent, FunctionComponent, useState } from "react";
|
|||||||
import { Upload } from "../../../Common/Upload/Upload";
|
import { Upload } from "../../../Common/Upload/Upload";
|
||||||
import { UploadDetailsRecord } from "../../../Contracts/ViewModels";
|
import { UploadDetailsRecord } from "../../../Contracts/ViewModels";
|
||||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
|
||||||
import { getErrorMessage } from "../../Tables/Utilities";
|
import { getErrorMessage } from "../../Tables/Utilities";
|
||||||
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
export interface UploadItemsPaneProps {
|
export const UploadItemsPane: FunctionComponent = () => {
|
||||||
explorer: Explorer;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ explorer }: UploadItemsPaneProps) => {
|
|
||||||
const [files, setFiles] = useState<FileList>();
|
const [files, setFiles] = useState<FileList>();
|
||||||
const [uploadFileData, setUploadFileData] = useState<UploadDetailsRecord[]>([]);
|
const [uploadFileData, setUploadFileData] = useState<UploadDetailsRecord[]>([]);
|
||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
@@ -25,7 +21,7 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ explo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedCollection = explorer.findSelectedCollection();
|
const selectedCollection = useSelectedNode.getState().findSelectedCollection();
|
||||||
setIsExecuting(true);
|
setIsExecuting(true);
|
||||||
|
|
||||||
selectedCollection
|
selectedCollection
|
||||||
|
|||||||
@@ -1,86 +1,16 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Delete Database Confirmation Pane submit() Should call delete database 1`] = `
|
exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||||
<DeleteDatabaseConfirmationPanel
|
<DeleteDatabaseConfirmationPanel
|
||||||
closePanel={[Function]}
|
|
||||||
explorer={
|
explorer={
|
||||||
Explorer {
|
Object {
|
||||||
"_isInitializingNotebooks": false,
|
|
||||||
"_resetNotebookWorkspace": [Function],
|
|
||||||
"canSaveQueries": [Function],
|
|
||||||
"collapsedResourceTreeWidth": 36,
|
|
||||||
"databases": [Function],
|
|
||||||
"isAccountReady": [Function],
|
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
|
||||||
"isHostedDataExplorerEnabled": [Function],
|
|
||||||
"isLastCollection": [Function],
|
|
||||||
"isLastNonEmptyDatabase": [Function],
|
|
||||||
"isNotebookEnabled": [Function],
|
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
|
||||||
"isResourceTokenCollectionNodeSelected": [Function],
|
|
||||||
"isSchemaEnabled": [Function],
|
|
||||||
"isSelectedDatabaseShared": [Function],
|
|
||||||
"isServerlessEnabled": [Function],
|
|
||||||
"isShellEnabled": [Function],
|
|
||||||
"isSynapseLinkUpdating": [Function],
|
|
||||||
"isTabsContentExpanded": [Function],
|
|
||||||
"memoryUsageInfo": [Function],
|
|
||||||
"notebookBasePath": [Function],
|
|
||||||
"notebookServerInfo": [Function],
|
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
|
||||||
"onRefreshResourcesClick": [Function],
|
|
||||||
"provideFeedbackEmail": [Function],
|
|
||||||
"queriesClient": QueriesClient {
|
|
||||||
"container": [Circular],
|
|
||||||
},
|
|
||||||
"refreshAllDatabases": [Function],
|
"refreshAllDatabases": [Function],
|
||||||
"refreshNotebookList": [Function],
|
|
||||||
"resourceTokenCollection": [Function],
|
|
||||||
"resourceTokenCollectionId": [Function],
|
|
||||||
"resourceTokenDatabaseId": [Function],
|
|
||||||
"resourceTokenPartitionKey": [Function],
|
|
||||||
"resourceTree": ResourceTreeAdapter {
|
|
||||||
"container": [Circular],
|
|
||||||
"copyNotebook": [Function],
|
|
||||||
"databaseCollectionIdMap": Map {},
|
|
||||||
"koSubsCollectionIdMap": Map {},
|
|
||||||
"koSubsDatabaseIdMap": Map {},
|
|
||||||
"parameters": [Function],
|
|
||||||
},
|
|
||||||
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
|
||||||
"container": [Circular],
|
|
||||||
"parameters": [Function],
|
|
||||||
},
|
|
||||||
"selectedDatabaseId": [Function],
|
|
||||||
"selectedNode": [Function],
|
|
||||||
"setInProgressConsoleDataIdToBeDeleted": undefined,
|
|
||||||
"setNotificationConsoleData": undefined,
|
|
||||||
"sparkClusterConnectionInfo": [Function],
|
|
||||||
"splitter": Splitter {
|
|
||||||
"bounds": Object {
|
|
||||||
"max": 400,
|
|
||||||
"min": 240,
|
|
||||||
},
|
|
||||||
"direction": "vertical",
|
|
||||||
"isCollapsed": [Function],
|
|
||||||
"leftSideId": "resourcetree",
|
|
||||||
"onResizeStart": [Function],
|
|
||||||
"onResizeStop": [Function],
|
|
||||||
"splitterId": "h_splitter1",
|
|
||||||
},
|
|
||||||
"tabsManager": TabsManager {
|
"tabsManager": TabsManager {
|
||||||
"activeTab": [Function],
|
"activeTab": [Function],
|
||||||
"openedTabs": [Function],
|
"openedTabs": [Function],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
openNotificationConsole={[Function]}
|
|
||||||
selectedDatabase={
|
|
||||||
Object {
|
|
||||||
"collections": [Function],
|
|
||||||
"id": [Function],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<RightPaneForm
|
<RightPaneForm
|
||||||
formError=""
|
formError=""
|
||||||
@@ -103,7 +33,7 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
verticalAlign="center"
|
verticalAlign="center"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-Stack panelInfoErrorContainer css-102"
|
className="ms-Stack panelInfoErrorContainer css-53"
|
||||||
>
|
>
|
||||||
<StyledIconBase
|
<StyledIconBase
|
||||||
aria-label="warning"
|
aria-label="warning"
|
||||||
@@ -392,7 +322,7 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
aria-label="warning"
|
aria-label="warning"
|
||||||
className="panelWarningIcon root-104"
|
className="panelWarningIcon root-55"
|
||||||
data-icon-name="WarningSolid"
|
data-icon-name="WarningSolid"
|
||||||
role="img"
|
role="img"
|
||||||
>
|
>
|
||||||
@@ -411,7 +341,7 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
aria-label="message"
|
aria-label="message"
|
||||||
className="panelWarningErrorMessage css-105"
|
className="panelWarningErrorMessage css-56"
|
||||||
>
|
>
|
||||||
Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.
|
Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.
|
||||||
</span>
|
</span>
|
||||||
@@ -435,7 +365,7 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="css-105"
|
className="css-56"
|
||||||
>
|
>
|
||||||
Confirm by typing the database id
|
Confirm by typing the database id
|
||||||
</span>
|
</span>
|
||||||
@@ -735,18 +665,18 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
validateOnLoad={true}
|
validateOnLoad={true}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField root-107"
|
className="ms-TextField root-58"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-wrapper"
|
className="ms-TextField-wrapper"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-fieldGroup fieldGroup-108"
|
className="ms-TextField-fieldGroup fieldGroup-59"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className="ms-TextField-field field-109"
|
className="ms-TextField-field field-60"
|
||||||
id="confirmDatabaseId"
|
id="confirmDatabaseId"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -769,7 +699,7 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="css-126"
|
className="css-69"
|
||||||
>
|
>
|
||||||
Help us improve Azure Cosmos DB!
|
Help us improve Azure Cosmos DB!
|
||||||
</span>
|
</span>
|
||||||
@@ -779,7 +709,7 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="css-126"
|
className="css-69"
|
||||||
>
|
>
|
||||||
What is the reason why you are deleting this database?
|
What is the reason why you are deleting this database?
|
||||||
</span>
|
</span>
|
||||||
@@ -1081,17 +1011,17 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
validateOnLoad={true}
|
validateOnLoad={true}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField ms-TextField--multiline root-107"
|
className="ms-TextField ms-TextField--multiline root-58"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-wrapper"
|
className="ms-TextField-wrapper"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-fieldGroup fieldGroup-127"
|
className="ms-TextField-fieldGroup fieldGroup-70"
|
||||||
>
|
>
|
||||||
<textarea
|
<textarea
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
className="ms-TextField-field field-128"
|
className="ms-TextField-field field-71"
|
||||||
id="deleteDatabaseFeedbackInput"
|
id="deleteDatabaseFeedbackInput"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -2817,7 +2747,7 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
aria-label="OK"
|
aria-label="OK"
|
||||||
className="ms-Button ms-Button--primary root-118"
|
className="ms-Button ms-Button--primary root-73"
|
||||||
data-is-focusable={true}
|
data-is-focusable={true}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
@@ -2829,16 +2759,16 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-flexContainer flexContainer-119"
|
className="ms-Button-flexContainer flexContainer-74"
|
||||||
data-automationid="splitbuttonprimary"
|
data-automationid="splitbuttonprimary"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-textContainer textContainer-120"
|
className="ms-Button-textContainer textContainer-75"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-label label-122"
|
className="ms-Button-label label-77"
|
||||||
id="id__3"
|
id="id__6"
|
||||||
key="id__3"
|
key="id__6"
|
||||||
>
|
>
|
||||||
OK
|
OK
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ jest.mock("../Explorer");
|
|||||||
|
|
||||||
const createExplorer = () => {
|
const createExplorer = () => {
|
||||||
const mock = new Explorer();
|
const mock = new Explorer();
|
||||||
mock.selectedNode = ko.observable();
|
|
||||||
mock.isNotebookEnabled = ko.observable(false);
|
mock.isNotebookEnabled = ko.observable(false);
|
||||||
mock.tabsManager = new TabsManager();
|
mock.tabsManager = new TabsManager();
|
||||||
return mock as jest.Mocked<Explorer>;
|
return mock as jest.Mocked<Explorer>;
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import { FeaturePanelLauncher } from "../Controls/FeaturePanel/FeaturePanelLaunc
|
|||||||
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
|
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity";
|
import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity";
|
||||||
|
import { useDatabases } from "../useDatabases";
|
||||||
|
import { useSelectedNode } from "../useSelectedNode";
|
||||||
|
|
||||||
export interface SplashScreenItem {
|
export interface SplashScreenItem {
|
||||||
iconSrc: string;
|
iconSrc: string;
|
||||||
@@ -59,7 +61,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
|
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
this.subscriptions.push(
|
this.subscriptions.push(
|
||||||
this.container.selectedNode.subscribe(() => this.setState({})),
|
{ dispose: useSelectedNode.subscribe(() => this.setState({})) },
|
||||||
this.container.isNotebookEnabled.subscribe(() => this.setState({}))
|
this.container.isNotebookEnabled.subscribe(() => this.setState({}))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -227,12 +229,12 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.container.isDatabaseNodeOrNoneSelected()) {
|
if (!useSelectedNode.getState().isDatabaseNodeOrNoneSelected()) {
|
||||||
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
|
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
|
||||||
items.push({
|
items.push({
|
||||||
iconSrc: NewQueryIcon,
|
iconSrc: NewQueryIcon,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = this.container.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null);
|
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null);
|
||||||
},
|
},
|
||||||
title: "New SQL Query",
|
title: "New SQL Query",
|
||||||
@@ -242,7 +244,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
items.push({
|
items.push({
|
||||||
iconSrc: NewQueryIcon,
|
iconSrc: NewQueryIcon,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = this.container.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null);
|
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null);
|
||||||
},
|
},
|
||||||
title: "New Query",
|
title: "New Query",
|
||||||
@@ -265,20 +267,14 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
title: "New Stored Procedure",
|
title: "New Stored Procedure",
|
||||||
description: null,
|
description: null,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = this.container.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null);
|
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scale & Settings */
|
/* Scale & Settings */
|
||||||
let isShared = false;
|
const isShared = useSelectedNode.getState().findSelectedDatabase()?.isDatabaseShared();
|
||||||
if (this.container.isDatabaseNodeSelected()) {
|
|
||||||
isShared = this.container.findSelectedDatabase().isDatabaseShared();
|
|
||||||
} else if (this.container.isNodeKindSelected("Collection")) {
|
|
||||||
const database: ViewModels.Database = this.container.findSelectedCollection().getDatabase();
|
|
||||||
isShared = database && database.isDatabaseShared();
|
|
||||||
}
|
|
||||||
|
|
||||||
const label = isShared ? "Settings" : "Scale & Settings";
|
const label = isShared ? "Settings" : "Scale & Settings";
|
||||||
items.push({
|
items.push({
|
||||||
@@ -286,7 +282,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
title: label,
|
title: label,
|
||||||
description: null,
|
description: null,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = this.container.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onSettingsClick();
|
selectedCollection && selectedCollection.onSettingsClick();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -308,8 +304,8 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
title: collectionId,
|
title: collectionId,
|
||||||
description: "Data",
|
description: "Data",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const collection = this.container.findCollection(databaseId, collectionId);
|
const collection = useDatabases.getState().findCollection(databaseId, collectionId);
|
||||||
collection && collection.openTab();
|
collection?.openTab();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -792,7 +792,7 @@ export default class QueryBuilderViewModel {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public checkIfClauseChanged(clause: QueryClauseViewModel): void {
|
public checkIfClauseChanged(): void {
|
||||||
this._queryViewModel.checkIfBuilderChanged(clause);
|
this._queryViewModel.checkIfBuilderChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export default class QueryClauseViewModel {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.and_or.subscribe((value) => {
|
this.and_or.subscribe((value) => {
|
||||||
this._queryBuilderViewModel.checkIfClauseChanged(this);
|
this._queryBuilderViewModel.checkIfClauseChanged();
|
||||||
});
|
});
|
||||||
this.field.subscribe((value) => {
|
this.field.subscribe((value) => {
|
||||||
this.changeField();
|
this.changeField();
|
||||||
@@ -103,13 +103,13 @@ export default class QueryClauseViewModel {
|
|||||||
// }
|
// }
|
||||||
});
|
});
|
||||||
this.customTimeValue.subscribe((value) => {
|
this.customTimeValue.subscribe((value) => {
|
||||||
this._queryBuilderViewModel.checkIfClauseChanged(this);
|
this._queryBuilderViewModel.checkIfClauseChanged();
|
||||||
});
|
});
|
||||||
this.value.subscribe((value) => {
|
this.value.subscribe((value) => {
|
||||||
this._queryBuilderViewModel.checkIfClauseChanged(this);
|
this._queryBuilderViewModel.checkIfClauseChanged();
|
||||||
});
|
});
|
||||||
this.operator.subscribe((value) => {
|
this.operator.subscribe((value) => {
|
||||||
this._queryBuilderViewModel.checkIfClauseChanged(this);
|
this._queryBuilderViewModel.checkIfClauseChanged();
|
||||||
});
|
});
|
||||||
this._groupCheckSubscription = this.checkedForGrouping.subscribe((value) => {
|
this._groupCheckSubscription = this.checkedForGrouping.subscribe((value) => {
|
||||||
this._queryBuilderViewModel.updateCanGroupClauses();
|
this._queryBuilderViewModel.updateCanGroupClauses();
|
||||||
@@ -184,7 +184,7 @@ export default class QueryClauseViewModel {
|
|||||||
this.type(QueryBuilderConstants.TableType.String);
|
this.type(QueryBuilderConstants.TableType.String);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._queryBuilderViewModel.checkIfClauseChanged(this);
|
this._queryBuilderViewModel.checkIfClauseChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private resetFromTimestamp(): void {
|
private resetFromTimestamp(): void {
|
||||||
@@ -216,7 +216,7 @@ export default class QueryClauseViewModel {
|
|||||||
this.timeValue("");
|
this.timeValue("");
|
||||||
this.customTimeValue("");
|
this.customTimeValue("");
|
||||||
}
|
}
|
||||||
this._queryBuilderViewModel.checkIfClauseChanged(this);
|
this._queryBuilderViewModel.checkIfClauseChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
// private customTimestampDialog(): Promise<any> {
|
// private customTimestampDialog(): Promise<any> {
|
||||||
|
|||||||
+29
-30
@@ -1,16 +1,18 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
|
import React from "react";
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
import { KeyCodes } from "../../../Common/Constants";
|
import { KeyCodes } from "../../../Common/Constants";
|
||||||
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
|
import { TableQuerySelectPanel } from "../../Panes/Tables/TableQuerySelectPanel/TableQuerySelectPanel";
|
||||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
||||||
import { getQuotedCqlIdentifier } from "../CqlUtilities";
|
import { getQuotedCqlIdentifier } from "../CqlUtilities";
|
||||||
import * as DataTableUtilities from "../DataTable/DataTableUtilities";
|
import * as DataTableUtilities from "../DataTable/DataTableUtilities";
|
||||||
import TableEntityListViewModel from "../DataTable/TableEntityListViewModel";
|
import TableEntityListViewModel from "../DataTable/TableEntityListViewModel";
|
||||||
import QueryBuilderViewModel from "./QueryBuilderViewModel";
|
import QueryBuilderViewModel from "./QueryBuilderViewModel";
|
||||||
import QueryClauseViewModel from "./QueryClauseViewModel";
|
|
||||||
|
|
||||||
export default class QueryViewModel {
|
export default class QueryViewModel {
|
||||||
public topValueLimitMessage: string = "Please input a number between 0 and 1000.";
|
public readonly topValueLimitMessage: string = "Please input a number between 0 and 1000.";
|
||||||
public queryBuilderViewModel = ko.observable<QueryBuilderViewModel>();
|
public queryBuilderViewModel = ko.observable<QueryBuilderViewModel>();
|
||||||
public isHelperActive = ko.observable<boolean>(true);
|
public isHelperActive = ko.observable<boolean>(true);
|
||||||
public isEditorActive = ko.observable<boolean>(false);
|
public isEditorActive = ko.observable<boolean>(false);
|
||||||
@@ -49,7 +51,7 @@ export default class QueryViewModel {
|
|||||||
this.queryTextIsReadOnly = ko.computed<boolean>(() => {
|
this.queryTextIsReadOnly = ko.computed<boolean>(() => {
|
||||||
return userContext.apiType !== "Cassandra";
|
return userContext.apiType !== "Cassandra";
|
||||||
});
|
});
|
||||||
let initialOptions = this._tableEntityListViewModel.headers;
|
const initialOptions = this._tableEntityListViewModel.headers;
|
||||||
this.columnOptions = ko.observableArray<string>(initialOptions);
|
this.columnOptions = ko.observableArray<string>(initialOptions);
|
||||||
this.focusTopResult = ko.observable<boolean>(false);
|
this.focusTopResult = ko.observable<boolean>(false);
|
||||||
this.focusExpandIcon = ko.observable<boolean>(false);
|
this.focusExpandIcon = ko.observable<boolean>(false);
|
||||||
@@ -63,12 +65,12 @@ export default class QueryViewModel {
|
|||||||
this.topValue() !== this.unchangedSaveTop()
|
this.topValue() !== this.unchangedSaveTop()
|
||||||
);
|
);
|
||||||
|
|
||||||
this.queryBuilderViewModel().clauseArray.subscribe((value) => {
|
this.queryBuilderViewModel().clauseArray.subscribe(() => {
|
||||||
this.setFilter();
|
this.setFilter();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.isExceedingLimit = ko.computed<boolean>(() => {
|
this.isExceedingLimit = ko.computed<boolean>(() => {
|
||||||
var currentTopValue: number = this.topValue();
|
const currentTopValue: number = this.topValue();
|
||||||
return currentTopValue < 0 || currentTopValue > 1000;
|
return currentTopValue < 0 || currentTopValue > 1000;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -111,7 +113,7 @@ export default class QueryViewModel {
|
|||||||
DataTableUtilities.forceRecalculateTableSize(); // Fix for 261924, forces the resize event so DataTableBindingManager will redo the calculation on table size.
|
DataTableUtilities.forceRecalculateTableSize(); // Fix for 261924, forces the resize event so DataTableBindingManager will redo the calculation on table size.
|
||||||
};
|
};
|
||||||
|
|
||||||
public ontoggleAdvancedOptionsKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
public ontoggleAdvancedOptionsKeyDown = (source: string, event: KeyboardEvent): boolean => {
|
||||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
||||||
this.toggleAdvancedOptions();
|
this.toggleAdvancedOptions();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@@ -125,31 +127,29 @@ export default class QueryViewModel {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private setFilter = (): string => {
|
private setFilter = (): string => {
|
||||||
var queryString = this.isEditorActive()
|
const queryString = this.isEditorActive()
|
||||||
? this.queryText()
|
? this.queryText()
|
||||||
: userContext.apiType === "Cassandra"
|
: userContext.apiType === "Cassandra"
|
||||||
? this.queryBuilderViewModel().getCqlFilterFromClauses()
|
? this.queryBuilderViewModel().getCqlFilterFromClauses()
|
||||||
: this.queryBuilderViewModel().getODataFilterFromClauses();
|
: this.queryBuilderViewModel().getODataFilterFromClauses();
|
||||||
var filter = queryString;
|
const filter = queryString;
|
||||||
this.queryText(filter);
|
this.queryText(filter);
|
||||||
return this.queryText();
|
return this.queryText();
|
||||||
};
|
};
|
||||||
|
|
||||||
private setSqlFilter = (): string => {
|
private setSqlFilter = (): string => {
|
||||||
var filter = this.queryBuilderViewModel().getSqlFilterFromClauses();
|
return this.queryBuilderViewModel().getSqlFilterFromClauses();
|
||||||
return filter;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private setCqlFilter = (): string => {
|
private setCqlFilter = (): string => {
|
||||||
var filter = this.queryBuilderViewModel().getCqlFilterFromClauses();
|
return this.queryBuilderViewModel().getCqlFilterFromClauses();
|
||||||
return filter;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public isHelperEnabled = ko
|
public isHelperEnabled = ko
|
||||||
.computed<boolean>(() => {
|
.computed<boolean>(() => {
|
||||||
return (
|
return (
|
||||||
this.queryText() === this.unchangedText() ||
|
this.queryText() === this.unchangedText() ||
|
||||||
this.queryText() === null ||
|
this.queryText() === undefined ||
|
||||||
this.queryText() === "" ||
|
this.queryText() === "" ||
|
||||||
this.isHelperActive()
|
this.isHelperActive()
|
||||||
);
|
);
|
||||||
@@ -159,13 +159,13 @@ export default class QueryViewModel {
|
|||||||
});
|
});
|
||||||
|
|
||||||
public runQuery = (): DataTables.DataTable => {
|
public runQuery = (): DataTables.DataTable => {
|
||||||
var filter = this.setFilter();
|
let filter = this.setFilter();
|
||||||
if (filter && userContext.apiType !== "Cassandra") {
|
if (filter && userContext.apiType !== "Cassandra") {
|
||||||
filter = filter.replace(/"/g, "'");
|
filter = filter.replace(/"/g, "'");
|
||||||
}
|
}
|
||||||
var top = this.topValue();
|
const top = this.topValue();
|
||||||
var selectOptions = this._getSelectedResults();
|
const selectOptions = this._getSelectedResults();
|
||||||
var select = selectOptions;
|
const select = selectOptions;
|
||||||
this._tableEntityListViewModel.tableQuery.filter = filter;
|
this._tableEntityListViewModel.tableQuery.filter = filter;
|
||||||
this._tableEntityListViewModel.tableQuery.top = top;
|
this._tableEntityListViewModel.tableQuery.top = top;
|
||||||
this._tableEntityListViewModel.tableQuery.select = select;
|
this._tableEntityListViewModel.tableQuery.select = select;
|
||||||
@@ -177,16 +177,16 @@ export default class QueryViewModel {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public clearQuery = (): DataTables.DataTable => {
|
public clearQuery = (): DataTables.DataTable => {
|
||||||
this.queryText(null);
|
this.queryText();
|
||||||
this.topValue(null);
|
this.topValue();
|
||||||
this.selectText(null);
|
this.selectText();
|
||||||
this.selectMessage("");
|
this.selectMessage("");
|
||||||
// clears the queryBuilder and adds a new blank clause
|
// clears the queryBuilder and adds a new blank clause
|
||||||
this.queryBuilderViewModel().queryClauses.removeAll();
|
this.queryBuilderViewModel().queryClauses.removeAll();
|
||||||
this.queryBuilderViewModel().addNewClause();
|
this.queryBuilderViewModel().addNewClause();
|
||||||
this._tableEntityListViewModel.tableQuery.filter = null;
|
this._tableEntityListViewModel.tableQuery.filter = undefined;
|
||||||
this._tableEntityListViewModel.tableQuery.top = null;
|
this._tableEntityListViewModel.tableQuery.top = undefined;
|
||||||
this._tableEntityListViewModel.tableQuery.select = null;
|
this._tableEntityListViewModel.tableQuery.select = undefined;
|
||||||
this._tableEntityListViewModel.oDataQuery("");
|
this._tableEntityListViewModel.oDataQuery("");
|
||||||
this._tableEntityListViewModel.sqlQuery("SELECT * FROM c");
|
this._tableEntityListViewModel.sqlQuery("SELECT * FROM c");
|
||||||
this._tableEntityListViewModel.cqlQuery(
|
this._tableEntityListViewModel.cqlQuery(
|
||||||
@@ -197,12 +197,11 @@ export default class QueryViewModel {
|
|||||||
return this._tableEntityListViewModel.reloadTable(false);
|
return this._tableEntityListViewModel.reloadTable(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
public selectQueryOptions(): Promise<any> {
|
public selectQueryOptions() {
|
||||||
this.queryTablesTab.container.openTableSelectQueryPanel(this);
|
useSidePanel.getState().openSidePanel("Select Column", <TableQuerySelectPanel queryViewModel={this} />);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onselectQueryOptionsKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
public onselectQueryOptionsKeyDown = (source: string, event: KeyboardEvent): boolean => {
|
||||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
||||||
this.selectQueryOptions();
|
this.selectQueryOptions();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@@ -212,7 +211,7 @@ export default class QueryViewModel {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public getSelectMessage(): void {
|
public getSelectMessage(): void {
|
||||||
if (_.isEmpty(this.selectText()) || this.selectText() === null) {
|
if (_.isEmpty(this.selectText()) || this.selectText() === undefined) {
|
||||||
this.selectMessage("");
|
this.selectMessage("");
|
||||||
} else {
|
} else {
|
||||||
this.selectMessage(`${this.selectText().length} of ${this.columnOptions().length} columns selected.`);
|
this.selectMessage(`${this.selectText().length} of ${this.columnOptions().length} columns selected.`);
|
||||||
@@ -220,7 +219,7 @@ export default class QueryViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public isSelected = ko.computed<boolean>(() => {
|
public isSelected = ko.computed<boolean>(() => {
|
||||||
return !(_.isEmpty(this.selectText()) || this.selectText() === null);
|
return !(_.isEmpty(this.selectText()) || this.selectText() === undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
private setCheckToSave(): void {
|
private setCheckToSave(): void {
|
||||||
@@ -230,7 +229,7 @@ export default class QueryViewModel {
|
|||||||
this.isSaveEnabled(false);
|
this.isSaveEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public checkIfBuilderChanged(clause: QueryClauseViewModel): void {
|
public checkIfBuilderChanged(): void {
|
||||||
this.setFilter();
|
this.setFilter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,6 @@ describe("Documents tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
hashLocation: "",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(documentsTab.buildQuery("")).toContain("select");
|
expect(documentsTab.buildQuery("")).toContain("select");
|
||||||
@@ -90,7 +89,6 @@ describe("Documents tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
hashLocation: "",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(documentsTab.showPartitionKey).toBe(false);
|
expect(documentsTab.showPartitionKey).toBe(false);
|
||||||
@@ -104,7 +102,6 @@ describe("Documents tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
hashLocation: "",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(documentsTab.showPartitionKey).toBe(false);
|
expect(documentsTab.showPartitionKey).toBe(false);
|
||||||
@@ -118,7 +115,6 @@ describe("Documents tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
hashLocation: "",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(documentsTab.showPartitionKey).toBe(true);
|
expect(documentsTab.showPartitionKey).toBe(true);
|
||||||
@@ -135,7 +131,6 @@ describe("Documents tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
hashLocation: "",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(documentsTab.showPartitionKey).toBe(false);
|
expect(documentsTab.showPartitionKey).toBe(false);
|
||||||
@@ -149,7 +144,6 @@ describe("Documents tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
hashLocation: "",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(documentsTab.showPartitionKey).toBe(true);
|
expect(documentsTab.showPartitionKey).toBe(true);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandBu
|
|||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { AccessibleVerticalList } from "../Tree/AccessibleVerticalList";
|
import { AccessibleVerticalList } from "../Tree/AccessibleVerticalList";
|
||||||
import DocumentId from "../Tree/DocumentId";
|
import DocumentId from "../Tree/DocumentId";
|
||||||
|
import { useSelectedNode } from "../useSelectedNode";
|
||||||
import template from "./DocumentsTab.html";
|
import template from "./DocumentsTab.html";
|
||||||
import TabsBase from "./TabsBase";
|
import TabsBase from "./TabsBase";
|
||||||
|
|
||||||
@@ -911,13 +912,13 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
iconSrc: UploadIcon,
|
iconSrc: UploadIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
selectedCollection && container.openUploadItemsPanePane();
|
selectedCollection && container.openUploadItemsPanePane();
|
||||||
},
|
},
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
hasPopup: true,
|
hasPopup: true,
|
||||||
disabled: container.isDatabaseNodeOrNoneSelected(),
|
disabled: useSelectedNode.getState().isDatabaseNodeOrNoneSelected(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Renders a Javascript object to be displayed inside Monaco Editor */
|
/** Renders a Javascript object to be displayed inside Monaco Editor */
|
||||||
protected renderObjectForEditor(value: any, replacer: any, space: string | number): string {
|
public renderObjectForEditor(value: any, replacer: any, space: string | number): string {
|
||||||
return MongoUtility.tojson(value, null, false);
|
return MongoUtility.tojson(value, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
|
||||||
import Q from "q";
|
|
||||||
import MongoUtility from "../../Common/MongoUtility";
|
|
||||||
import QueryTab from "./QueryTab";
|
|
||||||
import * as HeadersUtility from "../../Common/HeadersUtility";
|
|
||||||
import { queryIterator } from "../../Common/MongoProxyClient";
|
|
||||||
import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
|
|
||||||
|
|
||||||
export default class MongoQueryTab extends QueryTab {
|
|
||||||
public collection: ViewModels.Collection;
|
|
||||||
|
|
||||||
constructor(options: ViewModels.QueryTabOptions) {
|
|
||||||
options.queryText = ""; // override sql query editor content for now so we only display mongo related help items
|
|
||||||
super(options);
|
|
||||||
this.isPreferredApiMongoDB = true;
|
|
||||||
this.monacoSettings = new ViewModels.MonacoEditorSettings("plaintext", false);
|
|
||||||
}
|
|
||||||
/** Renders a Javascript object to be displayed inside Monaco Editor */
|
|
||||||
protected renderObjectForEditor(value: any, replacer: any, space: string | number): string {
|
|
||||||
return MongoUtility.tojson(value, null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _initIterator(): Q.Promise<MinimalQueryIterator> {
|
|
||||||
let options: any = {};
|
|
||||||
options.enableCrossPartitionQuery = HeadersUtility.shouldEnableCrossPartitionKey();
|
|
||||||
this._iterator = queryIterator(this.collection.databaseId, this.collection, this.sqlStatementToExecute());
|
|
||||||
return Q(this._iterator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import React from "react";
|
||||||
|
import MongoUtility from "../../../Common/MongoUtility";
|
||||||
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
import { NewQueryTab } from "../QueryTab/QueryTab";
|
||||||
|
import QueryTabComponent, { IQueryTabComponentProps, ITabAccessor } from "../QueryTab/QueryTabComponent";
|
||||||
|
|
||||||
|
export interface IMongoQueryTabProps {
|
||||||
|
container: Explorer;
|
||||||
|
viewModelcollection?: ViewModels.Collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NewMongoQueryTab extends NewQueryTab {
|
||||||
|
public collection: ViewModels.Collection;
|
||||||
|
public iMongoQueryTabComponentProps: IQueryTabComponentProps;
|
||||||
|
public queryText: string;
|
||||||
|
|
||||||
|
constructor(options: ViewModels.QueryTabOptions, private mongoQueryTabProps: IMongoQueryTabProps) {
|
||||||
|
super(options, mongoQueryTabProps);
|
||||||
|
this.queryText = "";
|
||||||
|
this.iMongoQueryTabComponentProps = {
|
||||||
|
collection: options.collection,
|
||||||
|
isExecutionError: this.isExecutionError(),
|
||||||
|
tabId: this.tabId,
|
||||||
|
tabsBaseInstance: this,
|
||||||
|
queryText: this.queryText,
|
||||||
|
partitionKey: this.partitionKey,
|
||||||
|
container: this.mongoQueryTabProps.container,
|
||||||
|
onTabAccessor: (instance: ITabAccessor): void => {
|
||||||
|
this.iTabAccessor = instance;
|
||||||
|
},
|
||||||
|
isPreferredApiMongoDB: true,
|
||||||
|
monacoEditorSetting: "plaintext",
|
||||||
|
viewModelcollection: this.mongoQueryTabProps.viewModelcollection,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Renders a Javascript object to be displayed inside Monaco Editor */
|
||||||
|
//eslint-disable-next-line
|
||||||
|
public renderObjectForEditor(value: any, replacer: any, space: string | number): string {
|
||||||
|
return MongoUtility.tojson(value, undefined, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
return <QueryTabComponent {...this.iMongoQueryTabComponentProps} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<iframe
|
|
||||||
name="explorer"
|
|
||||||
class="iframe"
|
|
||||||
style="width: 100%; height: 100%; border: 0px; padding: 0px; margin: 0px; overflow: hidden"
|
|
||||||
data-bind="
|
|
||||||
attr: {
|
|
||||||
src: url,
|
|
||||||
id: tabId
|
|
||||||
},
|
|
||||||
event:{
|
|
||||||
load: setContentFocus(event)
|
|
||||||
}"
|
|
||||||
title="Mongo Shell"
|
|
||||||
role="tabpanel"
|
|
||||||
></iframe>
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import React from "react";
|
||||||
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
|
import type { TabOptions } from "../../../Contracts/ViewModels";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
import TabsBase from "../TabsBase";
|
||||||
|
import MongoShellTabComponent, { IMongoShellTabAccessor, IMongoShellTabComponentProps } from "./MongoShellTabComponent";
|
||||||
|
|
||||||
|
export interface IMongoShellTabProps {
|
||||||
|
container: Explorer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NewMongoShellTab extends TabsBase {
|
||||||
|
public queryText: string;
|
||||||
|
public currentQuery: string;
|
||||||
|
public partitionKey: DataModels.PartitionKey;
|
||||||
|
public iMongoShellTabComponentProps: IMongoShellTabComponentProps;
|
||||||
|
public iMongoShellTabAccessor: IMongoShellTabAccessor;
|
||||||
|
|
||||||
|
constructor(options: TabOptions, private props: IMongoShellTabProps) {
|
||||||
|
super(options);
|
||||||
|
this.iMongoShellTabComponentProps = {
|
||||||
|
collection: this.collection,
|
||||||
|
tabsBaseInstance: this,
|
||||||
|
container: this.props.container,
|
||||||
|
onMongoShellTabAccessor: (instance: IMongoShellTabAccessor) => {
|
||||||
|
this.iMongoShellTabAccessor = instance;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
return <MongoShellTabComponent {...this.iMongoShellTabComponentProps} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
public onTabClick(): void {
|
||||||
|
this.manager?.activateTab(this);
|
||||||
|
this.iMongoShellTabAccessor.onTabClickEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
+90
-54
@@ -1,28 +1,71 @@
|
|||||||
import * as ko from "knockout";
|
import React, { Component } from "react";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import { configContext, Platform } from "../../ConfigContext";
|
import { configContext, Platform } from "../../../ConfigContext";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { isInvalidParentFrameOrigin, isReadyMessage } from "../../Utils/MessageValidation";
|
import { isInvalidParentFrameOrigin, isReadyMessage } from "../../../Utils/MessageValidation";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import template from "./MongoShellTab.html";
|
import TabsBase from "../TabsBase";
|
||||||
import TabsBase from "./TabsBase";
|
|
||||||
|
|
||||||
export default class MongoShellTab extends TabsBase {
|
//eslint-disable-next-line
|
||||||
public readonly html = template;
|
class MessageType {
|
||||||
public url: ko.Computed<string>;
|
static IframeReady = "iframeready";
|
||||||
private _container: Explorer;
|
static Notification = "notification";
|
||||||
|
static Log = "log";
|
||||||
|
}
|
||||||
|
|
||||||
|
//eslint-disable-next-line
|
||||||
|
class LogType {
|
||||||
|
static Information = "information";
|
||||||
|
static Warning = "warning";
|
||||||
|
static Verbose = "verbose";
|
||||||
|
static InProgress = "inprogress";
|
||||||
|
static StartTrace = "start";
|
||||||
|
static SuccessTrace = "success";
|
||||||
|
static FailureTrace = "failure";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMongoShellTabAccessor {
|
||||||
|
onTabClickEvent: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMongoShellTabComponentStates {
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMongoShellTabComponentProps {
|
||||||
|
collection: ViewModels.CollectionBase;
|
||||||
|
tabsBaseInstance: TabsBase;
|
||||||
|
container: Explorer;
|
||||||
|
onMongoShellTabAccessor: (instance: IMongoShellTabAccessor) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class MongoShellTabComponent extends Component<
|
||||||
|
IMongoShellTabComponentProps,
|
||||||
|
IMongoShellTabComponentStates
|
||||||
|
> {
|
||||||
private _runtimeEndpoint: string;
|
private _runtimeEndpoint: string;
|
||||||
private _logTraces: Map<string, number>;
|
private _logTraces: Map<string, number>;
|
||||||
|
|
||||||
constructor(options: ViewModels.TabOptions) {
|
constructor(props: IMongoShellTabComponentProps) {
|
||||||
super(options);
|
super(props);
|
||||||
this._logTraces = new Map();
|
this._logTraces = new Map();
|
||||||
this._container = options.collection.container;
|
|
||||||
this.url = ko.computed<string>(() => {
|
this.state = {
|
||||||
|
url: this.getURL(),
|
||||||
|
};
|
||||||
|
|
||||||
|
props.onMongoShellTabAccessor({
|
||||||
|
onTabClickEvent: this.onTabClick.bind(this),
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("message", this.handleMessage.bind(this), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getURL(): string {
|
||||||
const { databaseAccount: account } = userContext;
|
const { databaseAccount: account } = userContext;
|
||||||
const resourceId = account?.id;
|
const resourceId = account?.id;
|
||||||
const accountName = account?.name;
|
const accountName = account?.name;
|
||||||
@@ -36,32 +79,23 @@ export default class MongoShellTab extends TabsBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return `${extensionEndpoint}${baseUrl}index.html?resourceId=${resourceId}&accountName=${accountName}&mongoEndpoint=${mongoEndpoint}`;
|
return `${extensionEndpoint}${baseUrl}index.html?resourceId=${resourceId}&accountName=${accountName}&mongoEndpoint=${mongoEndpoint}`;
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("message", this.handleMessage.bind(this), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public setContentFocus(event: any): any {
|
//eslint-disable-next-line
|
||||||
// TODO: Work around cross origin security issue in Hosted Data Explorer by using Shell <-> Data Explorer messaging (253527)
|
public setContentFocus(event: React.SyntheticEvent<HTMLIFrameElement, Event>): void {}
|
||||||
// if(event.type === "load" && window.dataExplorerPlatform != PlatformType.Hosted) {
|
|
||||||
// let activeShell = event.target.contentWindow && event.target.contentWindow.mongo && event.target.contentWindow.mongo.shells && event.target.contentWindow.mongo.shells[0];
|
|
||||||
// activeShell && setTimeout(function(){
|
|
||||||
// activeShell.focus();
|
|
||||||
// },2000);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
public onTabClick(): void {
|
public onTabClick(): void {
|
||||||
super.onTabClick();
|
this.props.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
|
||||||
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleMessage(event: MessageEvent) {
|
public handleMessage(event: MessageEvent): void {
|
||||||
if (isInvalidParentFrameOrigin(event)) {
|
if (isInvalidParentFrameOrigin(event)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const shellIframe: HTMLIFrameElement = <HTMLIFrameElement>document.getElementById(this.tabId);
|
const shellIframe: HTMLIFrameElement = document.getElementById(
|
||||||
|
this.props.tabsBaseInstance.tabId
|
||||||
|
) as HTMLIFrameElement;
|
||||||
|
|
||||||
if (!shellIframe) {
|
if (!shellIframe) {
|
||||||
return;
|
return;
|
||||||
@@ -73,9 +107,9 @@ export default class MongoShellTab extends TabsBase {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.data.eventType == MessageType.IframeReady) {
|
if (event.data.eventType === MessageType.IframeReady) {
|
||||||
this.handleReadyMessage(event, shellIframe);
|
this.handleReadyMessage(event, shellIframe);
|
||||||
} else if (event.data.eventType == MessageType.Notification) {
|
} else if (event.data.eventType === MessageType.Notification) {
|
||||||
this.handleNotificationMessage(event, shellIframe);
|
this.handleNotificationMessage(event, shellIframe);
|
||||||
} else {
|
} else {
|
||||||
this.handleLogMessage(event, shellIframe);
|
this.handleLogMessage(event, shellIframe);
|
||||||
@@ -98,8 +132,8 @@ export default class MongoShellTab extends TabsBase {
|
|||||||
documentEndpoint.length -
|
documentEndpoint.length -
|
||||||
(Constants.MongoDBAccounts.protocol.length + 2 + Constants.MongoDBAccounts.defaultPort.length)
|
(Constants.MongoDBAccounts.protocol.length + 2 + Constants.MongoDBAccounts.defaultPort.length)
|
||||||
) + Constants.MongoDBAccounts.defaultPort.toString();
|
) + Constants.MongoDBAccounts.defaultPort.toString();
|
||||||
const databaseId = this.collection.databaseId;
|
const databaseId = this.props.collection.databaseId;
|
||||||
const collectionId = this.collection.id();
|
const collectionId = this.props.collection.id();
|
||||||
const apiEndpoint = configContext.BACKEND_ENDPOINT;
|
const apiEndpoint = configContext.BACKEND_ENDPOINT;
|
||||||
const encryptedAuthToken: string = userContext.accessToken;
|
const encryptedAuthToken: string = userContext.accessToken;
|
||||||
|
|
||||||
@@ -121,6 +155,7 @@ export default class MongoShellTab extends TabsBase {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//eslint-disable-next-line
|
||||||
private handleLogMessage(event: MessageEvent, shellIframe: HTMLIFrameElement) {
|
private handleLogMessage(event: MessageEvent, shellIframe: HTMLIFrameElement) {
|
||||||
if (!("logType" in event.data.data) || typeof event.data.data["logType"] !== "string") {
|
if (!("logType" in event.data.data) || typeof event.data.data["logType"] !== "string") {
|
||||||
return;
|
return;
|
||||||
@@ -144,6 +179,7 @@ export default class MongoShellTab extends TabsBase {
|
|||||||
TelemetryProcessor.trace(Action.MongoShell, ActionModifiers.Mark, dataToLog);
|
TelemetryProcessor.trace(Action.MongoShell, ActionModifiers.Mark, dataToLog);
|
||||||
break;
|
break;
|
||||||
case LogType.StartTrace:
|
case LogType.StartTrace:
|
||||||
|
//eslint-disable-next-line
|
||||||
const telemetryTraceId: number = TelemetryProcessor.traceStart(Action.MongoShell, dataToLog);
|
const telemetryTraceId: number = TelemetryProcessor.traceStart(Action.MongoShell, dataToLog);
|
||||||
this._logTraces.set(shellTraceId, telemetryTraceId);
|
this._logTraces.set(shellTraceId, telemetryTraceId);
|
||||||
break;
|
break;
|
||||||
@@ -168,6 +204,7 @@ export default class MongoShellTab extends TabsBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//eslint-disable-next-line
|
||||||
private handleNotificationMessage(event: MessageEvent, shellIframe: HTMLIFrameElement) {
|
private handleNotificationMessage(event: MessageEvent, shellIframe: HTMLIFrameElement) {
|
||||||
if (!("logType" in event.data.data) || typeof event.data.data["logType"] !== "string") {
|
if (!("logType" in event.data.data) || typeof event.data.data["logType"] !== "string") {
|
||||||
return;
|
return;
|
||||||
@@ -188,20 +225,19 @@ export default class MongoShellTab extends TabsBase {
|
|||||||
return logConsoleProgress(dataToLog);
|
return logConsoleProgress(dataToLog);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class MessageType {
|
render(): JSX.Element {
|
||||||
static IframeReady: string = "iframeready";
|
return (
|
||||||
static Notification: string = "notification";
|
<iframe
|
||||||
static Log: string = "log";
|
name="explorer"
|
||||||
|
className="iframe"
|
||||||
|
style={{ width: "100%", height: "100%", border: 0, padding: 0, margin: 0, overflow: "hidden" }}
|
||||||
|
src={this.state.url}
|
||||||
|
id={this.props.tabsBaseInstance.tabId}
|
||||||
|
onLoad={(event) => this.setContentFocus(event)}
|
||||||
|
title="Mongo Shell"
|
||||||
|
role="tabpanel"
|
||||||
|
></iframe>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class LogType {
|
|
||||||
static Information: string = "information";
|
|
||||||
static Warning: string = "warning";
|
|
||||||
static Verbose: string = "verbose";
|
|
||||||
static InProgress: string = "inprogress";
|
|
||||||
static StartTrace: string = "start";
|
|
||||||
static SuccessTrace: string = "success";
|
|
||||||
static FailureTrace: string = "failure";
|
|
||||||
}
|
}
|
||||||
@@ -1,335 +0,0 @@
|
|||||||
<div class="tab-pane" data-bind="attr:{id: tabId}" role="tabpanel">
|
|
||||||
<div class="tabPaneContentContainer">
|
|
||||||
<div class="mongoQueryHelper" data-bind="visible: isPreferredApiMongoDB && sqlQueryEditorContent().length === 0">
|
|
||||||
Start by writing a Mongo query, for example: <strong>{'id':'foo'}</strong> or <strong>{ }</strong> to get all the
|
|
||||||
documents.
|
|
||||||
</div>
|
|
||||||
<div class="warningErrorContainer" aria-live="assertive" data-bind="visible: maybeSubQuery">
|
|
||||||
<div class="warningErrorContent">
|
|
||||||
<span><img class="paneErrorIcon" src="/info_color.svg" alt="Error" /></span>
|
|
||||||
<span class="warningErrorDetailsLinkContainer">
|
|
||||||
We have detected you may be using a subquery. Non-correlated subqueries are not currently supported.
|
|
||||||
<a href="https://docs.microsoft.com/en-us/azure/cosmos-db/sql-query-subquery"
|
|
||||||
>Please see Cosmos sub query documentation for further information</a
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="queryEditorWithSplitter" data-bind="attr: { id: queryEditorId }">
|
|
||||||
<editor
|
|
||||||
class="queryEditor"
|
|
||||||
data-bind="css: { mongoQueryEditor: isPreferredApiMongoDB }"
|
|
||||||
params="{
|
|
||||||
content: initialEditorContent,
|
|
||||||
contentType: monacoSettings.language,
|
|
||||||
isReadOnly: monacoSettings.readOnly,
|
|
||||||
lineNumbers: 'on',
|
|
||||||
ariaLabel: 'Editing Query',
|
|
||||||
updatedContent: sqlQueryEditorContent,
|
|
||||||
selectedContent: selectedContent
|
|
||||||
}"
|
|
||||||
></editor>
|
|
||||||
<!-- Splitter - Start -->
|
|
||||||
<div class="splitter ui-resizable-handle ui-resizable-s" data-bind="attr: { id: splitterId }">
|
|
||||||
<img class="queryEditorHorizontalSplitter" src="/HorizontalSplitter.svg" alt="Splitter" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Splitter - End -->
|
|
||||||
|
|
||||||
<!-- Script for results metadata that is common to all APIs -->
|
|
||||||
<script type="text/html" id="result-metadata-template">
|
|
||||||
<span>
|
|
||||||
<span data-bind="text: showingDocumentsDisplayText"></span>
|
|
||||||
</span>
|
|
||||||
<span class="queryResultDivider" data-bind="visible: fetchNextPageButton.enabled"> | </span>
|
|
||||||
<span class="queryResultNextEnable" data-bind="visible: fetchNextPageButton.enabled">
|
|
||||||
<a data-bind="click: onFetchNextPageClick">
|
|
||||||
<span>Load more</span>
|
|
||||||
<img class="queryResultnextImg" src="/Query-Editor-Next.svg" alt="Fetch next page" />
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Query Errors Tab - Start-->
|
|
||||||
<div class="active queryErrorsHeaderContainer" data-bind="visible: !!error()">
|
|
||||||
<span class="queryErrors" data-toggle="tab" data-bind="attr: { href: '#queryerrors' + tabId }">Errors</span>
|
|
||||||
</div>
|
|
||||||
<!-- Query Errors Tab - End -->
|
|
||||||
|
|
||||||
<!-- Query Results & Errors Content Container - Start-->
|
|
||||||
<div class="queryResultErrorContentContainer">
|
|
||||||
<div
|
|
||||||
class="queryEditorWatermark"
|
|
||||||
data-bind="visible: allResultsMetadata().length === 0 && !error() && !queryResults() && !isExecuting()"
|
|
||||||
>
|
|
||||||
<p><img src="/RunQuery.png" alt="Execute Query Watermark" /></p>
|
|
||||||
<p class="queryEditorWatermarkText">Execute a query to see the results</p>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="queryResultsErrorsContent"
|
|
||||||
data-bind="visible: allResultsMetadata().length > 0 || !!error() || queryResults()"
|
|
||||||
>
|
|
||||||
<div class="togglesWithMetadata" data-bind="visible: !error()">
|
|
||||||
<div
|
|
||||||
class="toggles"
|
|
||||||
aria-label="Successful execution"
|
|
||||||
id="execute-query-toggles"
|
|
||||||
data-bind="event: { keydown: onToggleKeyDown }"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<div class="tab">
|
|
||||||
<input type="radio" class="radio" value="result" />
|
|
||||||
<span
|
|
||||||
class="toggleSwitch"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="click: toggleResult, css:{ selectedToggle: isResultToggled(), unselectedToggle: !isResultToggled() }"
|
|
||||||
aria-label="Results"
|
|
||||||
>Results</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="tab">
|
|
||||||
<input type="radio" class="radio" value="logs" />
|
|
||||||
<span
|
|
||||||
class="toggleSwitch"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="click: toggleMetrics, css:{ selectedToggle: isMetricsToggled(), unselectedToggle: !isMetricsToggled() }"
|
|
||||||
aria-label="Query stats"
|
|
||||||
>Query Stats</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="result-metadata"
|
|
||||||
data-bind="template: { name: 'result-metadata-template' }, visible: isResultToggled()"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<json-editor
|
|
||||||
params="{ content: queryResults, isReadOnly: true, ariaLabel: 'Query results' }"
|
|
||||||
data-bind="visible: queryResults() && queryResults().length > 0 && isResultToggled() && allResultsMetadata().length > 0 && !error()"
|
|
||||||
>
|
|
||||||
</json-editor>
|
|
||||||
<div
|
|
||||||
class="queryMetricsSummaryContainer"
|
|
||||||
data-bind="visible: isMetricsToggled() && allResultsMetadata().length > 0 && !error()"
|
|
||||||
>
|
|
||||||
<table class="queryMetricsSummary">
|
|
||||||
<caption>
|
|
||||||
Query Statistics
|
|
||||||
</caption>
|
|
||||||
<thead class="queryMetricsSummaryHead">
|
|
||||||
<tr class="queryMetricsSummaryHeader queryMetricsSummaryTuple">
|
|
||||||
<th title="METRIC" scope="col">METRIC</th>
|
|
||||||
<th title="VALUE" scope="col">VALUE</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="queryMetricsSummaryBody" data-bind="with: aggregatedQueryMetrics">
|
|
||||||
<tr class="queryMetricsSummaryTuple">
|
|
||||||
<td title="Request Charge">Request Charge</td>
|
|
||||||
<td>
|
|
||||||
<span
|
|
||||||
data-bind="text: $parent.requestChargeDisplayText, attr: { title: $parent.requestChargeDisplayText }"
|
|
||||||
></span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="queryMetricsSummaryTuple">
|
|
||||||
<td title="Showing Results">Showing Results</td>
|
|
||||||
<td>
|
|
||||||
<span
|
|
||||||
data-bind="text: $parent.showingDocumentsDisplayText, attr: { title: $parent.showingDocumentsDisplayText }"
|
|
||||||
></span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
|
|
||||||
<td>
|
|
||||||
<span title="Retrieved document count">Retrieved document count</span>
|
|
||||||
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="queryMetricTooltipText">Total number of retrieved documents</span>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td><span data-bind="text: retrievedDocumentCount, attr: { title: retrievedDocumentCount }"></span></td>
|
|
||||||
</tr>
|
|
||||||
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
|
|
||||||
<td>
|
|
||||||
<span title="Retrieved document size">Retrieved document size</span>
|
|
||||||
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="queryMetricTooltipText">Total size of retrieved documents in bytes</span>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span data-bind="text: retrievedDocumentSize, attr: { title: retrievedDocumentSize }"></span>
|
|
||||||
<span>bytes</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
|
|
||||||
<td>
|
|
||||||
<span title="Output document count">Output document count</span>
|
|
||||||
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="queryMetricTooltipText">Number of output documents</span>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td><span data-bind="text: outputDocumentCount, attr: { title: outputDocumentCount }"></span></td>
|
|
||||||
</tr>
|
|
||||||
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
|
|
||||||
<td>
|
|
||||||
<span title="Output document size">Output document size</span>
|
|
||||||
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="queryMetricTooltipText">Total size of output documents in bytes</span>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span data-bind="text: outputDocumentSize, attr: { title: outputDocumentSize }"></span>
|
|
||||||
<span>bytes</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
|
|
||||||
<td>
|
|
||||||
<span title="Index hit document count">Index hit document count</span>
|
|
||||||
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="queryMetricTooltipText">Total number of documents matched by the filter</span>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td><span data-bind="text: indexHitDocumentCount, attr: { title: indexHitDocumentCount }"></span></td>
|
|
||||||
</tr>
|
|
||||||
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
|
|
||||||
<td>
|
|
||||||
<span title="Index lookup time">Index lookup time</span>
|
|
||||||
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="queryMetricTooltipText">Time spent in physical index layer</span>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span data-bind="text: indexLookupTime, attr: { title: indexLookupTime }"></span> <span>ms</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
|
|
||||||
<td>
|
|
||||||
<span title="Document load time">Document load time</span>
|
|
||||||
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="queryMetricTooltipText">Time spent in loading documents</span>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span data-bind="text: documentLoadTime, attr: { title: documentLoadTime }"></span> <span>ms</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
|
|
||||||
<td>
|
|
||||||
<span title="Query engine execution time">Query engine execution time</span>
|
|
||||||
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="queryMetricTooltipText queryEngineExeTimeInfo"
|
|
||||||
>Time spent by the query engine to execute the query expression (excludes other execution times
|
|
||||||
like load documents or write results)</span
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span
|
|
||||||
data-bind="text: runtimeExecutionTimes.queryEngineExecutionTime, attr: { title: runtimeExecutionTimes.queryEngineExecutionTime }"
|
|
||||||
></span>
|
|
||||||
<span>ms</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
|
|
||||||
<td>
|
|
||||||
<span title="System function execution time">System function execution time</span>
|
|
||||||
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="queryMetricTooltipText">Total time spent executing system (built-in) functions</span>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span
|
|
||||||
data-bind="text: runtimeExecutionTimes.systemFunctionExecutionTime, attr: { title: runtimeExecutionTimes.systemFunctionExecutionTime }"
|
|
||||||
></span>
|
|
||||||
<span>ms</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
|
|
||||||
<td>
|
|
||||||
<span title="User defined function execution time">User defined function execution time</span>
|
|
||||||
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="queryMetricTooltipText">Total time spent executing user-defined functions</span>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span
|
|
||||||
data-bind="text: runtimeExecutionTimes.userDefinedFunctionExecutionTime, attr: { title: runtimeExecutionTimes.userDefinedFunctionExecutionTime }"
|
|
||||||
></span>
|
|
||||||
<span>ms</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
|
|
||||||
<td>
|
|
||||||
<span title="Document write time">Document write time</span>
|
|
||||||
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="queryMetricTooltipText">Time spent to write query result set to response buffer</span>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span data-bind="text: documentWriteTime, attr: { title: documentWriteTime }"></span> <span>ms</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.roundTrips() != null">
|
|
||||||
<td title="Round Trips">Round Trips</td>
|
|
||||||
<td><span data-bind="text: $parent.roundTrips, attr: { title: $parent.roundTrips }"></span></td>
|
|
||||||
</tr>
|
|
||||||
<!-- TODO: Report activity id for mongo queries -->
|
|
||||||
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.activityId() != null">
|
|
||||||
<td title="Activity id">Activity id</td>
|
|
||||||
<td></td>
|
|
||||||
<td><span data-bind="text: $parent.activityId, attr: { title: $parent.activityId }"></span></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div class="downloadMetricsLinkContainer" data-bind="visible: $parent.isQueryMetricsEnabled">
|
|
||||||
<a
|
|
||||||
id="downloadMetricsLink"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="event: { click: onDownloadQueryMetricsCsvClick, keypress: onDownloadQueryMetricsCsvKeyPress }"
|
|
||||||
>
|
|
||||||
<img class="downloadCsvImg" src="/DownloadQuery.svg" alt="download query metrics csv" />
|
|
||||||
<span>Per-partition query metrics (CSV)</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Query Errors Content - Start-->
|
|
||||||
<div
|
|
||||||
class="tab-pane active"
|
|
||||||
data-bind="
|
|
||||||
id: {
|
|
||||||
href: 'queryerrors' + tabId
|
|
||||||
},
|
|
||||||
visible: !!error()"
|
|
||||||
>
|
|
||||||
<div class="errorContent">
|
|
||||||
<span class="errorMessage" data-bind="text: error"></span>
|
|
||||||
<span class="errorDetailsLink">
|
|
||||||
<a
|
|
||||||
data-bind="click: $parent.onErrorDetailsClick, event: { keypress: $parent.onErrorDetailsKeyPress }"
|
|
||||||
id="error-display"
|
|
||||||
tabindex="0"
|
|
||||||
aria-label="Error details link"
|
|
||||||
>More details</a
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Query Errors Content - End-->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Results & Errors Content Container - End-->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,311 +0,0 @@
|
|||||||
@import "../../../less/Common/Constants";
|
|
||||||
@import "../../../less/Common/TabCommon";
|
|
||||||
|
|
||||||
@MongoQueryEditorHeight: 50px;
|
|
||||||
@ResultsTextFontWeight: 600;
|
|
||||||
@ToggleHeight: 30px;
|
|
||||||
@ToggleWidth: 250px;
|
|
||||||
@QueryEngineExeInfo: 305px;
|
|
||||||
|
|
||||||
.tab-pane {
|
|
||||||
.tabContentContainer();
|
|
||||||
|
|
||||||
.tabPaneContentContainer {
|
|
||||||
.tabContentContainer();
|
|
||||||
|
|
||||||
.mongoQueryHelper {
|
|
||||||
margin:@DefaultSpace 0px 0px 44px;
|
|
||||||
position: absolute;
|
|
||||||
top: 115px; //this is to avoid the jump of query editor
|
|
||||||
}
|
|
||||||
|
|
||||||
.queryEditorWithSplitter {
|
|
||||||
.flex-display();
|
|
||||||
.flex-direction();
|
|
||||||
flex-shrink: 0;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
margin-left: @SmallSpace;
|
|
||||||
|
|
||||||
.queryEditor {
|
|
||||||
.flex-display();
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
margin-top: @SmallSpace;
|
|
||||||
|
|
||||||
.jsonEditor {
|
|
||||||
border: none;
|
|
||||||
margin-top: @SmallSpace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.queryEditor.mongoQueryEditor {
|
|
||||||
margin-top: 32px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.queryEditorHorizontalSplitter {
|
|
||||||
margin: auto;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.queryErrorsHeaderContainer {
|
|
||||||
padding: 24px @LargeSpace 0px @MediumSpace;
|
|
||||||
|
|
||||||
.queryErrors {
|
|
||||||
font-size: @mediumFontSize;
|
|
||||||
list-style-type: none;
|
|
||||||
color: @BaseDark;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-left: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.queryResultErrorContentContainer {
|
|
||||||
.flex-display();
|
|
||||||
.flex-direction();
|
|
||||||
font-size: @DefaultFontSize;
|
|
||||||
padding: @DefaultSpace;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.queryEditorWatermark {
|
|
||||||
text-align: center;
|
|
||||||
margin: auto;
|
|
||||||
height: 25vh; // this is to align the water mark in center of the layout.
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin-bottom: @LargeSpace;
|
|
||||||
color: @BaseHigh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.queryEditorWatermarkText {
|
|
||||||
color: @BaseHigh;
|
|
||||||
font-size: @DefaultFontSize;
|
|
||||||
font-family: @DataExplorerFont;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.queryResultsErrorsContent {
|
|
||||||
height: 100%;
|
|
||||||
margin-left: @MediumSpace;
|
|
||||||
.flex-display();
|
|
||||||
.flex-direction();
|
|
||||||
|
|
||||||
|
|
||||||
.togglesWithMetadata {
|
|
||||||
margin-top: @MediumSpace;
|
|
||||||
|
|
||||||
.toggles {
|
|
||||||
height: @ToggleHeight;
|
|
||||||
width: @ToggleWidth;
|
|
||||||
margin-left: @MediumSpace;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab {
|
|
||||||
margin-right: @MediumSpace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggleSwitch {
|
|
||||||
.toggleSwitch();
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectedToggle {
|
|
||||||
.selectedToggle();
|
|
||||||
}
|
|
||||||
|
|
||||||
.unselectedToggle {
|
|
||||||
.unselectedToggle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-metadata {
|
|
||||||
padding: @LargeSpace @SmallSpace @MediumSpace @MediumSpace;
|
|
||||||
|
|
||||||
.queryResultDivider {
|
|
||||||
margin-left: @SmallSpace;
|
|
||||||
margin-right: @SmallSpace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.queryResultNextEnable {
|
|
||||||
color: @AccentMediumHigh;
|
|
||||||
font-size: @mediumFontSize;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
img {
|
|
||||||
height: @ImgHeight;
|
|
||||||
width: @ImgWidth;
|
|
||||||
margin-left: @SmallSpace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.queryResultNextDisable {
|
|
||||||
color: @BaseMediumHigh;
|
|
||||||
opacity: 0.5;
|
|
||||||
font-size: @mediumFontSize;
|
|
||||||
|
|
||||||
img {
|
|
||||||
height: @ImgHeight;
|
|
||||||
width: @ImgWidth;
|
|
||||||
margin-left: @SmallSpace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-pane.active {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.errorContent {
|
|
||||||
.flex-display();
|
|
||||||
width: 60%;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-size: @mediumFontSize;
|
|
||||||
padding: 0px @MediumSpace 0px @MediumSpace;
|
|
||||||
|
|
||||||
.errorMessage {
|
|
||||||
padding: @SmallSpace;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.errorDetailsLink {
|
|
||||||
cursor: pointer;
|
|
||||||
padding: @SmallSpace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.queryMetricsSummaryContainer {
|
|
||||||
.flex-display();
|
|
||||||
.flex-direction();
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.queryMetricsSummary {
|
|
||||||
margin: @LargeSpace @LargeSpace 0px @DefaultSpace;
|
|
||||||
table-layout: fixed;
|
|
||||||
display: block;
|
|
||||||
height: auto;
|
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
|
|
||||||
caption {
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.queryMetricsSummaryHead {
|
|
||||||
.flex-display();
|
|
||||||
}
|
|
||||||
|
|
||||||
.queryMetricsSummaryHeader.queryMetricsSummaryTuple {
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.queryMetricsSummaryBody {
|
|
||||||
.flex-display();
|
|
||||||
.flex-direction();
|
|
||||||
}
|
|
||||||
|
|
||||||
.queryMetricsSummaryTuple {
|
|
||||||
border-bottom: 1px solid @BaseMedium;
|
|
||||||
height: 32px;
|
|
||||||
font-size: 12px;
|
|
||||||
width: 100%;
|
|
||||||
.flex-display();
|
|
||||||
th, td {
|
|
||||||
padding: @DefaultSpace;
|
|
||||||
|
|
||||||
&:nth-child(1) {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
flex: 0 0 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(3) {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
flex: 0 0 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.queryMetricInfoTooltip {
|
|
||||||
.infoTooltip();
|
|
||||||
|
|
||||||
&:hover .queryMetricTooltipText {
|
|
||||||
.tooltipVisible();
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus .queryMetricTooltipText {
|
|
||||||
.tooltipVisible();
|
|
||||||
}
|
|
||||||
|
|
||||||
.queryMetricTooltipText {
|
|
||||||
top: -50px;
|
|
||||||
width: auto;
|
|
||||||
white-space: nowrap;
|
|
||||||
left: 6px;
|
|
||||||
visibility: hidden;
|
|
||||||
background-color: @BaseHigh;
|
|
||||||
color: @BaseLight;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
padding: @MediumSpace;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
border-width: (2 * @MediumSpace) (2 * @MediumSpace) 0px 0px;
|
|
||||||
bottom: -14px;
|
|
||||||
.tooltipTextAfter();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.queryEngineExeTimeInfo {
|
|
||||||
width: @QueryEngineExeInfo;
|
|
||||||
top: -85px;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.downloadMetricsLinkContainer {
|
|
||||||
margin: 24px 0px 24px @MediumSpace;
|
|
||||||
|
|
||||||
#downloadMetricsLink {
|
|
||||||
color: @BaseHigh;
|
|
||||||
padding: @DefaultSpace;
|
|
||||||
font-size: @mediumFontSize;
|
|
||||||
border: @ButtonBorderWidth solid @BaseLight;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.hover();
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
border: @ButtonBorderWidth dashed @AccentMedium;
|
|
||||||
.active();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
json-editor {
|
|
||||||
.flex-display();
|
|
||||||
.flex-direction();
|
|
||||||
overflow: hidden;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
padding: @SmallSpace 0px @SmallSpace @MediumSpace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
import { DatabaseAccount } from "../../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
|
||||||
import { updateUserContext } from "../../UserContext";
|
|
||||||
import Explorer from "../Explorer";
|
|
||||||
import QueryTab from "./QueryTab";
|
|
||||||
|
|
||||||
describe("Query Tab", () => {
|
|
||||||
function getNewQueryTabForContainer(container: Explorer): QueryTab {
|
|
||||||
const database = {
|
|
||||||
container: container,
|
|
||||||
id: ko.observable<string>("test"),
|
|
||||||
isDatabaseShared: () => false,
|
|
||||||
} as ViewModels.Database;
|
|
||||||
const collection = {
|
|
||||||
container: container,
|
|
||||||
databaseId: "test",
|
|
||||||
id: ko.observable<string>("test"),
|
|
||||||
} as ViewModels.Collection;
|
|
||||||
|
|
||||||
return new QueryTab({
|
|
||||||
tabKind: ViewModels.CollectionTabKind.Query,
|
|
||||||
collection: collection,
|
|
||||||
database: database,
|
|
||||||
title: "",
|
|
||||||
tabPath: "",
|
|
||||||
hashLocation: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("shouldSetSystemPartitionKeyContainerPartitionKeyValueUndefined", () => {
|
|
||||||
const collection = {
|
|
||||||
id: ko.observable<string>("withoutsystempk"),
|
|
||||||
partitionKey: {
|
|
||||||
systemKey: true,
|
|
||||||
},
|
|
||||||
} as ViewModels.Collection;
|
|
||||||
|
|
||||||
it("no container with system pk, should not set partition key option", () => {
|
|
||||||
const iteratorOptions = QueryTab.getIteratorOptions(collection);
|
|
||||||
expect(iteratorOptions.initialHeaders).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("isQueryMetricsEnabled()", () => {
|
|
||||||
let explorer: Explorer;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
explorer = new Explorer();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be true for accounts using SQL API", () => {
|
|
||||||
updateUserContext({});
|
|
||||||
const queryTab = getNewQueryTabForContainer(explorer);
|
|
||||||
expect(queryTab.isQueryMetricsEnabled()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be false for accounts using other APIs", () => {
|
|
||||||
updateUserContext({
|
|
||||||
databaseAccount: {
|
|
||||||
properties: {
|
|
||||||
capabilities: [{ name: "EnableGremlin" }],
|
|
||||||
},
|
|
||||||
} as DatabaseAccount,
|
|
||||||
});
|
|
||||||
const queryTab = getNewQueryTabForContainer(explorer);
|
|
||||||
expect(queryTab.isQueryMetricsEnabled()).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Save Queries command button", () => {
|
|
||||||
let explorer: Explorer;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
explorer = new Explorer();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be visible when using a supported API", () => {
|
|
||||||
updateUserContext({});
|
|
||||||
const queryTab = getNewQueryTabForContainer(explorer);
|
|
||||||
expect(queryTab.saveQueryButton.visible()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not be visible when using an unsupported API", () => {
|
|
||||||
updateUserContext({
|
|
||||||
databaseAccount: {
|
|
||||||
properties: {
|
|
||||||
capabilities: [{ name: "EnableMongo" }],
|
|
||||||
},
|
|
||||||
} as DatabaseAccount,
|
|
||||||
});
|
|
||||||
const queryTab = getNewQueryTabForContainer(explorer);
|
|
||||||
expect(queryTab.saveQueryButton.visible()).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user