mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-25 11:51:07 +00:00
Compare commits
70 Commits
tsStrict/f
...
users/srna
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c7f4d1ddf | ||
|
|
78e5c88e3c | ||
|
|
c0bd74ce1b | ||
|
|
c04ba728ef | ||
|
|
127b16cfc8 | ||
|
|
0f281c7a64 | ||
|
|
659d5a6677 | ||
|
|
5f66f113af | ||
|
|
b1c238f43a | ||
|
|
445d2650a2 | ||
|
|
447db01647 | ||
|
|
4d2a6999d4 | ||
|
|
a7239c7579 | ||
|
|
c1d4008895 | ||
|
|
59655eed5f | ||
|
|
6b35ab03f2 | ||
|
|
738a02a0f3 | ||
|
|
b392bed1b0 | ||
|
|
f255387ccd | ||
|
|
f9bd12eaa6 | ||
|
|
39215dc4de | ||
|
|
96e6bba38b | ||
|
|
c9fa44f6f4 | ||
|
|
05f59307c2 | ||
|
|
1d449e5b52 | ||
|
|
6f68c75257 | ||
|
|
914c372f5b | ||
|
|
af71a96d54 | ||
|
|
239c7edf7b | ||
|
|
0c6324a4c1 | ||
|
|
615bfeaf48 | ||
|
|
3bc58a80e4 | ||
|
|
5da9724deb | ||
|
|
999fad3bad | ||
|
|
baa3252ba8 | ||
|
|
959d34d88d | ||
|
|
ce3c2fcfb6 | ||
|
|
0a1a2bf421 | ||
|
|
b0bbeb188a | ||
|
|
fc9f287d0a | ||
|
|
006230262c | ||
|
|
6de77a4fba | ||
|
|
c980af9a5c | ||
|
|
c632342a43 | ||
|
|
bcc9f8dd32 | ||
|
|
fc9f4c5583 | ||
|
|
8f6cac3d35 | ||
|
|
2c296ede35 | ||
|
|
16b09df5fa | ||
|
|
ee60f61cfe | ||
|
|
f296c00a1c | ||
|
|
7d0be7d355 | ||
|
|
04b3ef051a | ||
|
|
b875407d49 | ||
|
|
18ce8749ed | ||
|
|
5e2b8d7df0 | ||
|
|
da13a2b3cf | ||
|
|
69b8196cf0 | ||
|
|
5417e1e120 | ||
|
|
481ff9e7fe | ||
|
|
e41b52e265 | ||
|
|
75d01f655f | ||
|
|
50f83cde87 | ||
|
|
6d03cec139 | ||
|
|
cb1d60cc90 | ||
|
|
0201e6ff92 | ||
|
|
1bcb4246f6 | ||
|
|
e7e15c54b3 | ||
|
|
522fdc69ab | ||
|
|
bfdeae56d9 |
@@ -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,15 +104,10 @@ 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
|
||||||
src/Explorer/Panes/BrowseQueriesPane.ts
|
src/Explorer/Panes/BrowseQueriesPane.ts
|
||||||
src/Explorer/Panes/ContextualPaneBase.ts
|
|
||||||
# src/Explorer/Panes/GraphStylingPane.ts
|
|
||||||
# src/Explorer/Panes/NewVertexPane.ts
|
|
||||||
src/Explorer/Panes/RenewAdHocAccessPane.ts
|
src/Explorer/Panes/RenewAdHocAccessPane.ts
|
||||||
src/Explorer/Panes/SetupNotebooksPane.ts
|
src/Explorer/Panes/SetupNotebooksPane.ts
|
||||||
src/Explorer/Panes/SwitchDirectoryPane.ts
|
src/Explorer/Panes/SwitchDirectoryPane.ts
|
||||||
@@ -138,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
|
||||||
@@ -148,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
|
||||||
@@ -208,9 +198,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
|
||||||
@@ -266,7 +253,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
|
||||||
|
|||||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -92,11 +92,11 @@ jobs:
|
|||||||
name: dist
|
name: dist
|
||||||
path: dist/
|
path: dist/
|
||||||
- name: Upload build to preview blob storage
|
- name: Upload build to preview blob storage
|
||||||
run: az storage blob upload-batch -d '$web' -s 'dist' --account-name cosmosexplorerpreview --subscription cosmosdb-portalteam-generaldemo --destination-path "${{github.event.pull_request.head.sha}}" --account-key="${PREVIEW_STORAGE_KEY}"
|
run: az storage blob upload-batch -d '$web' -s 'dist' --account-name cosmosexplorerpreview --subscription cosmosdb-portalteam-generaldemo --destination-path "${{github.event.pull_request.head.sha || github.sha}}" --account-key="${PREVIEW_STORAGE_KEY}"
|
||||||
env:
|
env:
|
||||||
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
||||||
- name: Upload preview config to blob storage
|
- name: Upload preview config to blob storage
|
||||||
run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --subscription cosmosdb-portalteam-generaldemo --name "${{github.event.pull_request.head.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}"
|
run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --subscription cosmosdb-portalteam-generaldemo --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}"
|
||||||
env:
|
env:
|
||||||
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
||||||
endtoendemulator:
|
endtoendemulator:
|
||||||
|
|||||||
2
.github/workflows/cleanup.yml
vendored
2
.github/workflows/cleanup.yml
vendored
@@ -7,7 +7,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
# Once every hour
|
# Once every hour
|
||||||
- cron: "0 * * * *"
|
- cron: "0 15 * * *"
|
||||||
|
|
||||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -3085,3 +3064,14 @@ settings-pane {
|
|||||||
padding-left: @SmallSpace;
|
padding-left: @SmallSpace;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.hiddenMain {
|
||||||
|
display: none;
|
||||||
|
height: 0px;
|
||||||
|
}
|
||||||
|
.spinner {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
background: white;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|||||||
@@ -200,4 +200,12 @@
|
|||||||
|
|
||||||
.migration:disabled {
|
.migration:disabled {
|
||||||
background-color: #ccc;
|
background-color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger-field {
|
||||||
|
width: 40%;
|
||||||
|
margin-top: 10px
|
||||||
|
}
|
||||||
|
.trigger-form {
|
||||||
|
padding: 10px 30px 10px 30px;
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
.resourceTree {
|
.resourceTree {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 20%;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
.main {
|
.main {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
132
package-lock.json
generated
132
package-lock.json
generated
@@ -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": {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"PROXY_PATH": "/proxy"
|
"PROXY_PATH": "/proxy",
|
||||||
|
"msalRedirectURI": "https://cosmos-explorer-preview.azurewebsites.net/"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,17 @@ app.get("/pull/:pr(\\d+)", (req, res) => {
|
|||||||
})
|
})
|
||||||
.catch(() => res.sendStatus(500));
|
.catch(() => res.sendStatus(500));
|
||||||
});
|
});
|
||||||
|
app.get("/", (req, res) => {
|
||||||
|
fetch("https://api.github.com/repos/Azure/cosmos-explorer/branches/master")
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then(({ commit: { sha } }) => {
|
||||||
|
const explorer = new URL(
|
||||||
|
"https://cosmos-explorer-preview.azurewebsites.net/commit/" + sha + "/hostedExplorer.html"
|
||||||
|
);
|
||||||
|
return res.redirect(explorer.href);
|
||||||
|
})
|
||||||
|
.catch(() => res.sendStatus(500));
|
||||||
|
});
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Example app listening on port: ${port}`);
|
console.log(`Example app listening on port: ${port}`);
|
||||||
|
|||||||
36
src/Common/CollapsedResourceTree.tsx
Normal file
36
src/Common/CollapsedResourceTree.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import React, { FunctionComponent } from "react";
|
||||||
|
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
|
||||||
|
import { userContext } from "../UserContext";
|
||||||
|
|
||||||
|
export interface CollapsedResourceTreeProps {
|
||||||
|
toggleLeftPaneExpanded: () => void;
|
||||||
|
isLeftPaneExpanded: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps> = ({
|
||||||
|
toggleLeftPaneExpanded,
|
||||||
|
isLeftPaneExpanded,
|
||||||
|
}: CollapsedResourceTreeProps): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<div id="mini" className={!isLeftPaneExpanded ? "mini toggle-mini" : "hiddenMain"}>
|
||||||
|
<div className="main-nav nav">
|
||||||
|
<ul className="nav">
|
||||||
|
<li
|
||||||
|
className="resourceTreeCollapse"
|
||||||
|
id="collapseToggleLeftPaneButton"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label="Expand Tree"
|
||||||
|
>
|
||||||
|
<span className="leftarrowCollapsed" onClick={toggleLeftPaneExpanded}>
|
||||||
|
<img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" />
|
||||||
|
</span>
|
||||||
|
<span className="collectionCollapsed" onClick={toggleLeftPaneExpanded}>
|
||||||
|
<span>{userContext.apiType} API</span>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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";
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export function client(): Cosmos.CosmosClient {
|
|||||||
if (_client) return _client;
|
if (_client) return _client;
|
||||||
const options: Cosmos.CosmosClientOptions = {
|
const options: Cosmos.CosmosClientOptions = {
|
||||||
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
|
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
|
||||||
...(!userContext.features.enableAadDataPlane && { key: userContext.masterKey }),
|
key: userContext.masterKey,
|
||||||
tokenProvider,
|
tokenProvider,
|
||||||
connectionPolicy: {
|
connectionPolicy: {
|
||||||
enableEndpointDiscovery: false,
|
enableEndpointDiscovery: false,
|
||||||
|
|||||||
17
src/Common/DatabaseAccountUtility.ts
Normal file
17
src/Common/DatabaseAccountUtility.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { userContext } from "../UserContext";
|
||||||
|
|
||||||
|
function isVirtualNetworkFilterEnabled() {
|
||||||
|
return userContext.databaseAccount?.properties?.isVirtualNetworkFilterEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isIpRulesEnabled() {
|
||||||
|
return userContext.databaseAccount?.properties?.ipRules?.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPrivateEndpointConnectionsEnabled() {
|
||||||
|
return userContext.databaseAccount?.properties?.privateEndpointConnections?.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPublicInternetAccessAllowed(): boolean {
|
||||||
|
return !isVirtualNetworkFilterEnabled() && !isIpRulesEnabled() && !isPrivateEndpointConnectionsEnabled();
|
||||||
|
}
|
||||||
@@ -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,45 +98,35 @@ 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)
|
if (!document) {
|
||||||
.then(
|
return undefined;
|
||||||
(results: ViewModels.QueryResults) => {
|
}
|
||||||
let queries: DataModels.Query[] = _.map(results.documents, (document: DataModels.Query) => {
|
const { id, resourceId, query, queryName } = document;
|
||||||
if (!document) {
|
const parsedQuery: DataModels.Query = {
|
||||||
return undefined;
|
resourceId: resourceId,
|
||||||
}
|
queryName: queryName,
|
||||||
const { id, resourceId, query, queryName } = document;
|
query: query,
|
||||||
const parsedQuery: DataModels.Query = {
|
id: id,
|
||||||
resourceId: resourceId,
|
};
|
||||||
queryName: queryName,
|
try {
|
||||||
query: query,
|
this.validateQuery(parsedQuery);
|
||||||
id: id,
|
return parsedQuery;
|
||||||
};
|
} catch (error) {
|
||||||
try {
|
return undefined;
|
||||||
this.validateQuery(parsedQuery);
|
}
|
||||||
return parsedQuery;
|
});
|
||||||
} catch (error) {
|
queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery);
|
||||||
return undefined;
|
NotificationConsoleUtils.logConsoleInfo("Successfully fetched saved queries");
|
||||||
}
|
clearMessage();
|
||||||
});
|
return queries;
|
||||||
queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery);
|
|
||||||
NotificationConsoleUtils.logConsoleInfo("Successfully fetched saved queries");
|
|
||||||
return Promise.resolve(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) {
|
||||||
|
|||||||
59
src/Common/ResourceTree.tsx
Normal file
59
src/Common/ResourceTree.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import React, { FunctionComponent } from "react";
|
||||||
|
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
|
||||||
|
import refreshImg from "../../images/refresh-cosmos.svg";
|
||||||
|
import { AuthType } from "../AuthType";
|
||||||
|
import { userContext } from "../UserContext";
|
||||||
|
|
||||||
|
export interface ResourceTreeProps {
|
||||||
|
toggleLeftPaneExpanded: () => void;
|
||||||
|
isLeftPaneExpanded: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ResourceTree: FunctionComponent<ResourceTreeProps> = ({
|
||||||
|
toggleLeftPaneExpanded,
|
||||||
|
isLeftPaneExpanded,
|
||||||
|
}: ResourceTreeProps): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<div id="main" className={isLeftPaneExpanded ? "main" : "hiddenMain"}>
|
||||||
|
{/* Collections Window - - Start */}
|
||||||
|
<div id="mainslide" className="flexContainer">
|
||||||
|
{/* Collections Window Title/Command Bar - Start */}
|
||||||
|
<div className="collectiontitle">
|
||||||
|
<div className="coltitle">
|
||||||
|
<span className="titlepadcol">{userContext.apiType} API</span>
|
||||||
|
<div className="float-right">
|
||||||
|
<span
|
||||||
|
className="padimgcolrefresh"
|
||||||
|
data-test="refreshTree"
|
||||||
|
role="button"
|
||||||
|
data-bind="click: onRefreshResourcesClick, clickBubble: false, event: { keypress: onRefreshDatabasesKeyPress }"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label="Refresh tree"
|
||||||
|
title="Refresh tree"
|
||||||
|
>
|
||||||
|
<img className="refreshcol" src={refreshImg} alt="Refresh Tree" />
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="padimgcolrefresh1"
|
||||||
|
id="expandToggleLeftPaneButton"
|
||||||
|
role="button"
|
||||||
|
onClick={toggleLeftPaneExpanded}
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label="Collapse Tree"
|
||||||
|
title="Collapse Tree"
|
||||||
|
>
|
||||||
|
<img className="refreshcol1" src={arrowLeftImg} alt="Hide" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{userContext.authType === AuthType.ResourceToken ? (
|
||||||
|
<div style={{ overflowY: "auto" }} data-bind="react:resourceTreeForResourceToken" />
|
||||||
|
) : (
|
||||||
|
<div style={{ overflowY: "auto" }} data-bind="react:resourceTree" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{/* Collections Window - End */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export interface ConfigContext {
|
|||||||
armAPIVersion?: string;
|
armAPIVersion?: string;
|
||||||
allowedJunoOrigins: string[];
|
allowedJunoOrigins: string[];
|
||||||
enableSchemaAnalyzer: boolean;
|
enableSchemaAnalyzer: boolean;
|
||||||
|
msalRedirectURI?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default configuration
|
// Default configuration
|
||||||
@@ -119,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) {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export interface DatabaseAccountExtendedProperties {
|
|||||||
enableAnalyticalStorage?: boolean;
|
enableAnalyticalStorage?: boolean;
|
||||||
isVirtualNetworkFilterEnabled?: boolean;
|
isVirtualNetworkFilterEnabled?: boolean;
|
||||||
ipRules?: IpRule[];
|
ipRules?: IpRule[];
|
||||||
|
privateEndpointConnections?: unknown[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatabaseAccountResponseLocation {
|
export interface DatabaseAccountResponseLocation {
|
||||||
@@ -391,16 +392,6 @@ export interface GeospatialConfig {
|
|||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GatewayDatabaseAccount {
|
|
||||||
MediaLink: string;
|
|
||||||
DatabasesLink: string;
|
|
||||||
MaxMediaStorageUsageInMB: number;
|
|
||||||
CurrentMediaStorageUsageInMB: number;
|
|
||||||
EnableMultipleWriteLocations?: boolean;
|
|
||||||
WritableLocations: RegionEndpoint[];
|
|
||||||
ReadableLocations: RegionEndpoint[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RegionEndpoint {
|
export interface RegionEndpoint {
|
||||||
name: string;
|
name: string;
|
||||||
documentAccountEndpoint: string;
|
documentAccountEndpoint: string;
|
||||||
@@ -421,13 +412,6 @@ export interface AccountKeys {
|
|||||||
secondaryReadonlyMasterKey: string;
|
secondaryReadonlyMasterKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AfecFeature {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
properties: { state: string };
|
|
||||||
type: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OperationStatus {
|
export interface OperationStatus {
|
||||||
status: string;
|
status: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -507,91 +491,6 @@ export interface MongoParameters extends RpParameters {
|
|||||||
analyticalStorageTtl?: number;
|
analyticalStorageTtl?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SparkClusterLibrary {
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Library extends SparkClusterLibrary {
|
|
||||||
properties: {
|
|
||||||
kind: "Jar";
|
|
||||||
source: {
|
|
||||||
kind: "HttpsUri";
|
|
||||||
uri: string;
|
|
||||||
libraryFileName: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LibraryFeedResponse {
|
|
||||||
value: Library[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ArmResource {
|
|
||||||
id: string;
|
|
||||||
location: string;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
tags: { [key: string]: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ArcadiaWorkspaceIdentity {
|
|
||||||
type: string;
|
|
||||||
principalId: string;
|
|
||||||
tenantId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ArcadiaWorkspaceProperties {
|
|
||||||
managedResourceGroupName: string;
|
|
||||||
provisioningState: string;
|
|
||||||
sqlAdministratorLogin: string;
|
|
||||||
connectivityEndpoints: {
|
|
||||||
artifacts: string;
|
|
||||||
dev: string;
|
|
||||||
spark: string;
|
|
||||||
sql: string;
|
|
||||||
web: string;
|
|
||||||
};
|
|
||||||
defaultDataLakeStorage: {
|
|
||||||
accountUrl: string;
|
|
||||||
filesystem: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ArcadiaWorkspaceFeedResponse {
|
|
||||||
value: ArcadiaWorkspace[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ArcadiaWorkspace extends ArmResource {
|
|
||||||
identity: ArcadiaWorkspaceIdentity;
|
|
||||||
properties: ArcadiaWorkspaceProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SparkPoolFeedResponse {
|
|
||||||
value: SparkPool[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SparkPoolProperties {
|
|
||||||
creationDate: string;
|
|
||||||
sparkVersion: string;
|
|
||||||
nodeCount: number;
|
|
||||||
nodeSize: string;
|
|
||||||
nodeSizeFamily: string;
|
|
||||||
provisioningState: string;
|
|
||||||
autoScale: {
|
|
||||||
enabled: boolean;
|
|
||||||
minNodeCount: number;
|
|
||||||
maxNodeCount: number;
|
|
||||||
};
|
|
||||||
autoPause: {
|
|
||||||
enabled: boolean;
|
|
||||||
delayInMinutes: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SparkPool extends ArmResource {
|
|
||||||
properties: SparkPoolProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MemoryUsageInfo {
|
export interface MemoryUsageInfo {
|
||||||
freeKB: number;
|
freeKB: number;
|
||||||
totalKB: number;
|
totalKB: number;
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ import {
|
|||||||
Resource,
|
Resource,
|
||||||
StoredProcedureDefinition,
|
StoredProcedureDefinition,
|
||||||
TriggerDefinition,
|
TriggerDefinition,
|
||||||
UserDefinedFunctionDefinition,
|
UserDefinedFunctionDefinition
|
||||||
} from "@azure/cosmos";
|
} from "@azure/cosmos";
|
||||||
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
|
|
||||||
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";
|
||||||
@@ -15,6 +14,7 @@ import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
|||||||
import Trigger from "../Explorer/Tree/Trigger";
|
import Trigger from "../Explorer/Tree/Trigger";
|
||||||
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
|
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
|
||||||
import { SelfServeType } from "../SelfServe/SelfServeUtils";
|
import { SelfServeType } from "../SelfServe/SelfServeUtils";
|
||||||
|
import { CollectionCreationDefaults } from "../UserContext";
|
||||||
import { SqlTriggerResource } from "../Utils/arm/generatedClients/cosmos/types";
|
import { SqlTriggerResource } from "../Utils/arm/generatedClients/cosmos/types";
|
||||||
import * as DataModels from "./DataModels";
|
import * as DataModels from "./DataModels";
|
||||||
import { SubscriptionType } from "./SubscriptionType";
|
import { SubscriptionType } from "./SubscriptionType";
|
||||||
@@ -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,8 +274,6 @@ export interface TabOptions {
|
|||||||
tabKind: CollectionTabKind;
|
tabKind: CollectionTabKind;
|
||||||
title: string;
|
title: string;
|
||||||
tabPath: string;
|
tabPath: string;
|
||||||
hashLocation: string;
|
|
||||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]) => void;
|
|
||||||
isTabsContentExpanded?: ko.Observable<boolean>;
|
isTabsContentExpanded?: ko.Observable<boolean>;
|
||||||
onLoadStartKey?: number;
|
onLoadStartKey?: number;
|
||||||
|
|
||||||
@@ -287,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 {
|
||||||
@@ -372,6 +370,7 @@ export enum TerminalKind {
|
|||||||
Default = 0,
|
Default = 0,
|
||||||
Mongo = 1,
|
Mongo = 1,
|
||||||
Cassandra = 2,
|
Cassandra = 2,
|
||||||
|
PostgreSQL = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataExplorerInputsFrame {
|
export interface DataExplorerInputsFrame {
|
||||||
@@ -411,25 +410,6 @@ export interface SelfServeFrameInputs {
|
|||||||
flights?: readonly string[];
|
flights?: readonly string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionCreationDefaults {
|
|
||||||
storage: string;
|
|
||||||
throughput: ThroughputDefaults;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ThroughputDefaults {
|
|
||||||
fixed: number;
|
|
||||||
unlimited:
|
|
||||||
| number
|
|
||||||
| {
|
|
||||||
collectionThreshold: number;
|
|
||||||
lessThanOrEqualToThreshold: number;
|
|
||||||
greatThanThreshold: number;
|
|
||||||
};
|
|
||||||
unlimitedmax: number;
|
|
||||||
unlimitedmin: number;
|
|
||||||
shared: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MonacoEditorSettings {
|
export class MonacoEditorSettings {
|
||||||
public readonly language: string;
|
public readonly language: string;
|
||||||
public readonly readOnly: boolean;
|
public readonly readOnly: boolean;
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
jest.mock("monaco-editor");
|
|
||||||
|
|
||||||
import * as ko from "knockout";
|
|
||||||
import "./ComponentRegisterer";
|
|
||||||
|
|
||||||
describe("Component Registerer", () => {
|
|
||||||
it("should register json-editor component", () => {
|
|
||||||
expect(ko.components.isRegistered("json-editor")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should register dynamic-list component", () => {
|
|
||||||
expect(ko.components.isRegistered("dynamic-list")).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,12 +1,8 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent";
|
import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent";
|
||||||
import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent";
|
|
||||||
import { EditorComponent } from "./Controls/Editor/EditorComponent";
|
import { EditorComponent } from "./Controls/Editor/EditorComponent";
|
||||||
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
|
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
|
||||||
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
|
|
||||||
|
|
||||||
ko.components.register("editor", new EditorComponent());
|
ko.components.register("editor", new EditorComponent());
|
||||||
ko.components.register("json-editor", new JsonEditorComponent());
|
ko.components.register("json-editor", new JsonEditorComponent());
|
||||||
ko.components.register("diff-editor", new DiffEditorComponent());
|
ko.components.register("diff-editor", new DiffEditorComponent());
|
||||||
ko.components.register("dynamic-list", DynamicListComponent);
|
|
||||||
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
|
||||||
|
|||||||
@@ -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 { 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: container.addCollectionText(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (userContext.apiType !== "Tables") {
|
|
||||||
items.push({
|
|
||||||
iconSrc: DeleteDatabaseIcon,
|
|
||||||
onClick: () => container.openDeleteDatabaseConfirmationPane(),
|
|
||||||
label: container.deleteDatabaseText(),
|
|
||||||
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: container.deleteCollectionText(),
|
|
||||||
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",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
182
src/Explorer/ContextMenuButtonFactory.tsx
Normal file
182
src/Explorer/ContextMenuButtonFactory.tsx
Normal file
@@ -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,142 +0,0 @@
|
|||||||
import { DefaultButton, IButtonStyles, IContextualMenuItem, IContextualMenuProps } from "@fluentui/react";
|
|
||||||
import * as React from "react";
|
|
||||||
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
|
|
||||||
import * as Logger from "../../../Common/Logger";
|
|
||||||
import { ArcadiaWorkspace, SparkPool } from "../../../Contracts/DataModels";
|
|
||||||
|
|
||||||
export interface ArcadiaMenuPickerProps {
|
|
||||||
selectText?: string;
|
|
||||||
disableSubmenu?: boolean;
|
|
||||||
selectedSparkPool: string;
|
|
||||||
workspaces: ArcadiaWorkspaceItem[];
|
|
||||||
onSparkPoolSelect: (
|
|
||||||
e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
|
|
||||||
item: IContextualMenuItem
|
|
||||||
) => boolean | void;
|
|
||||||
onCreateNewWorkspaceClicked: () => boolean | void;
|
|
||||||
onCreateNewSparkPoolClicked: (workspaceResourceId: string) => boolean | void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ArcadiaMenuPickerStates {
|
|
||||||
selectedSparkPool: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ArcadiaWorkspaceItem extends ArcadiaWorkspace {
|
|
||||||
sparkPools: SparkPool[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, ArcadiaMenuPickerStates> {
|
|
||||||
constructor(props: ArcadiaMenuPickerProps) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
selectedSparkPool: props.selectedSparkPool,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private _onSparkPoolClicked = (
|
|
||||||
e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
|
|
||||||
item: IContextualMenuItem
|
|
||||||
): boolean | void => {
|
|
||||||
try {
|
|
||||||
this.props.onSparkPoolSelect(e, item);
|
|
||||||
this.setState({
|
|
||||||
selectedSparkPool: item.text,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
Logger.logError(getErrorMessage(error), "ArcadiaMenuPicker/_onSparkPoolClicked");
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private _onCreateNewWorkspaceClicked = (
|
|
||||||
e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
|
|
||||||
item: IContextualMenuItem
|
|
||||||
): boolean | void => {
|
|
||||||
this.props.onCreateNewWorkspaceClicked();
|
|
||||||
};
|
|
||||||
|
|
||||||
private _onCreateNewSparkPoolClicked = (
|
|
||||||
e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
|
|
||||||
item: IContextualMenuItem
|
|
||||||
): boolean | void => {
|
|
||||||
this.props.onCreateNewSparkPoolClicked(item.key);
|
|
||||||
};
|
|
||||||
|
|
||||||
public render() {
|
|
||||||
const { workspaces } = this.props;
|
|
||||||
let workspaceMenuItems: IContextualMenuItem[] = workspaces.map((workspace) => {
|
|
||||||
let sparkPoolsMenuProps: IContextualMenuProps = {
|
|
||||||
items: workspace.sparkPools.map(
|
|
||||||
(sparkpool): IContextualMenuItem => ({
|
|
||||||
key: sparkpool.id,
|
|
||||||
text: sparkpool.name,
|
|
||||||
onClick: this._onSparkPoolClicked,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
};
|
|
||||||
if (!sparkPoolsMenuProps.items.length) {
|
|
||||||
sparkPoolsMenuProps.items.push({
|
|
||||||
key: workspace.id,
|
|
||||||
text: "Create new spark pool",
|
|
||||||
onClick: this._onCreateNewSparkPoolClicked,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: workspace.id,
|
|
||||||
text: workspace.name,
|
|
||||||
subMenuProps: this.props.disableSubmenu ? undefined : sparkPoolsMenuProps,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!workspaceMenuItems.length) {
|
|
||||||
workspaceMenuItems.push({
|
|
||||||
key: "create_workspace",
|
|
||||||
text: "Create new workspace",
|
|
||||||
onClick: this._onCreateNewWorkspaceClicked,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const dropdownStyle: IButtonStyles = {
|
|
||||||
root: {
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
margin: "auto 5px",
|
|
||||||
padding: "0",
|
|
||||||
border: "0",
|
|
||||||
},
|
|
||||||
rootHovered: {
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
},
|
|
||||||
rootChecked: {
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
},
|
|
||||||
rootFocused: {
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
},
|
|
||||||
rootExpanded: {
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
},
|
|
||||||
flexContainer: {
|
|
||||||
height: "30px",
|
|
||||||
border: "1px solid #a6a6a6",
|
|
||||||
padding: "0 8px",
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
fontWeight: "400",
|
|
||||||
fontSize: "12px",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DefaultButton
|
|
||||||
text={this.state.selectedSparkPool || this.props.selectText || "Select a Spark pool"}
|
|
||||||
persistMenu={true}
|
|
||||||
className="arcadia-menu-picker"
|
|
||||||
menuProps={{
|
|
||||||
items: workspaceMenuItems,
|
|
||||||
}}
|
|
||||||
styles={dropdownStyle}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,12 @@
|
|||||||
import * as StringUtils from "../../../Utils/StringUtils";
|
|
||||||
import { KeyCodes } from "../../../Common/Constants";
|
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* React component for Command button component.
|
* React component for Command button component.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ArcadiaMenuPickerProps } from "../Arcadia/ArcadiaMenuPicker";
|
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
||||||
|
import { KeyCodes } from "../../../Common/Constants";
|
||||||
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import * as StringUtils from "../../../Utils/StringUtils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for this component
|
* Options for this component
|
||||||
@@ -114,15 +111,6 @@ export interface CommandButtonComponentProps {
|
|||||||
* Aria-label for the button
|
* Aria-label for the button
|
||||||
*/
|
*/
|
||||||
ariaLabel: string;
|
ariaLabel: string;
|
||||||
//TODO: generalize customized command bar
|
|
||||||
/**
|
|
||||||
* If set to true, will render arcadia picker
|
|
||||||
*/
|
|
||||||
isArcadiaPicker?: boolean;
|
|
||||||
/**
|
|
||||||
* props to render arcadia picker
|
|
||||||
*/
|
|
||||||
arcadiaProps?: ArcadiaMenuPickerProps;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CommandButtonComponent extends React.Component<CommandButtonComponentProps> {
|
export class CommandButtonComponent extends React.Component<CommandButtonComponentProps> {
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
import { DynamicListComponent, DynamicListParams, DynamicListItem } from "./DynamicListComponent";
|
|
||||||
|
|
||||||
const $ = (selector: string) => document.querySelector(selector) as HTMLElement;
|
|
||||||
|
|
||||||
function buildComponent(buttonOptions: any) {
|
|
||||||
document.body.innerHTML = DynamicListComponent.template as any;
|
|
||||||
const vm = new DynamicListComponent.viewModel(buttonOptions);
|
|
||||||
ko.applyBindings(vm);
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("Dynamic List Component", () => {
|
|
||||||
const mockPlaceHolder = "Write here";
|
|
||||||
const mockButton = "Add something";
|
|
||||||
const mockValue = "/someText";
|
|
||||||
const mockAriaLabel = "Add ariaLabel";
|
|
||||||
const items: ko.ObservableArray<DynamicListItem> = ko.observableArray<DynamicListItem>();
|
|
||||||
|
|
||||||
function buildListOptions(
|
|
||||||
items: ko.ObservableArray<DynamicListItem>,
|
|
||||||
placeholder?: string,
|
|
||||||
mockButton?: string
|
|
||||||
): DynamicListParams {
|
|
||||||
return {
|
|
||||||
placeholder: placeholder,
|
|
||||||
listItems: items,
|
|
||||||
buttonText: mockButton,
|
|
||||||
ariaLabel: mockAriaLabel,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
ko.cleanNode(document);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Rendering", () => {
|
|
||||||
it("should display button text", () => {
|
|
||||||
const params = buildListOptions(items, mockPlaceHolder, mockButton);
|
|
||||||
buildComponent(params);
|
|
||||||
expect($(".dynamicListItemAdd").textContent).toContain(mockButton);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Behavior", () => {
|
|
||||||
it("should add items to the list", () => {
|
|
||||||
const params = buildListOptions(items, mockPlaceHolder, mockButton);
|
|
||||||
buildComponent(params);
|
|
||||||
$(".dynamicListItemAdd").click();
|
|
||||||
expect(items().length).toBe(1);
|
|
||||||
const input = document.getElementsByClassName("dynamicListItem").item(0).children[0];
|
|
||||||
input.setAttribute("value", mockValue);
|
|
||||||
input.dispatchEvent(new Event("change"));
|
|
||||||
input.dispatchEvent(new Event("blur"));
|
|
||||||
expect(items()[0].value()).toBe(mockValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should remove items from the list", () => {
|
|
||||||
const params = buildListOptions(items, mockPlaceHolder);
|
|
||||||
buildComponent(params);
|
|
||||||
$(".dynamicListItemDelete").click();
|
|
||||||
expect(items().length).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
@import "../../../../less/Common/Constants";
|
|
||||||
|
|
||||||
.dynamicList {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.dynamicListContainer {
|
|
||||||
.dynamicListItem {
|
|
||||||
justify-content: space-around;
|
|
||||||
margin-bottom: @MediumSpace;
|
|
||||||
|
|
||||||
input {
|
|
||||||
width: @newCollectionPaneInputWidth;
|
|
||||||
margin: auto;
|
|
||||||
font-size: @mediumFontSize;
|
|
||||||
padding: @SmallSpace @DefaultSpace;
|
|
||||||
color: @BaseDark;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dynamicListItemDelete {
|
|
||||||
padding: @SmallSpace @SmallSpace @DefaultSpace;
|
|
||||||
margin-left: @SmallSpace;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.hover();
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
.active();
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
.dataExplorerIcons();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dynamicListItemNew {
|
|
||||||
margin-top: @LargeSpace;
|
|
||||||
|
|
||||||
.dynamicListItemAdd {
|
|
||||||
padding: @DefaultSpace;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.hover();
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
.active();
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
.dataExplorerIcons();
|
|
||||||
margin: 0px @SmallSpace @SmallSpace 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
/**
|
|
||||||
* Dynamic list:
|
|
||||||
*
|
|
||||||
* Creates a list of dynamic inputs that can be populated and deleted.
|
|
||||||
*
|
|
||||||
* How to use in your markup:
|
|
||||||
* <dynamic-list params="{ listItems: anObservableArrayOfDynamicListItem, placeholder: 'Text to display in placeholder', ariaLabel: 'Text for aria-label', buttonText: 'Add item' }">
|
|
||||||
* </dynamic-list>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as ko from "knockout";
|
|
||||||
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
|
|
||||||
import { KeyCodes } from "../../../Common/Constants";
|
|
||||||
import template from "./dynamic-list.html";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parameters for this component
|
|
||||||
*/
|
|
||||||
export interface DynamicListParams {
|
|
||||||
/**
|
|
||||||
* Observable list of items to update
|
|
||||||
*/
|
|
||||||
listItems: ko.ObservableArray<DynamicListItem>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Placeholder text to use on inputs
|
|
||||||
*/
|
|
||||||
placeholder?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Text to use as aria-label
|
|
||||||
*/
|
|
||||||
ariaLabel: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Text for the button to add items
|
|
||||||
*/
|
|
||||||
buttonText?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback triggered when the template is bound to the component (for testing purposes)
|
|
||||||
*/
|
|
||||||
onTemplateReady?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Item in the dynamic list
|
|
||||||
*/
|
|
||||||
export interface DynamicListItem {
|
|
||||||
value: ko.Observable<string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DynamicListViewModel extends WaitsForTemplateViewModel {
|
|
||||||
public placeholder: string;
|
|
||||||
public ariaLabel: string;
|
|
||||||
public buttonText: string;
|
|
||||||
public newItem: ko.Observable<string>;
|
|
||||||
public isTemplateReady: ko.Observable<boolean>;
|
|
||||||
public listItems: ko.ObservableArray<DynamicListItem>;
|
|
||||||
|
|
||||||
public constructor(options: DynamicListParams) {
|
|
||||||
super();
|
|
||||||
super.onTemplateReady((isTemplateReady: boolean) => {
|
|
||||||
if (isTemplateReady && options.onTemplateReady) {
|
|
||||||
options.onTemplateReady();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const params: DynamicListParams = options;
|
|
||||||
const paramsPlaceholder: string = params.placeholder;
|
|
||||||
const paramsButtonText: string = params.buttonText;
|
|
||||||
this.placeholder = paramsPlaceholder || "Write a value";
|
|
||||||
this.ariaLabel = "Unique keys";
|
|
||||||
this.buttonText = paramsButtonText || "Add item";
|
|
||||||
this.listItems = params.listItems || ko.observableArray<DynamicListItem>();
|
|
||||||
this.newItem = ko.observable("");
|
|
||||||
}
|
|
||||||
|
|
||||||
public removeItem = (data: any, event: MouseEvent | KeyboardEvent): void => {
|
|
||||||
const context = ko.contextFor(event.target as Node);
|
|
||||||
this.listItems.splice(context.$index(), 1);
|
|
||||||
document.getElementById("addUniqueKeyBtn").focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
public onRemoveItemKeyPress = (data: any, event: KeyboardEvent, source: any): boolean => {
|
|
||||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
|
||||||
this.removeItem(data, event);
|
|
||||||
(document.querySelector(".dynamicListItem:last-of-type input") as HTMLElement).focus();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
public addItem(): void {
|
|
||||||
this.listItems.push({ value: ko.observable("") });
|
|
||||||
(document.querySelector(".dynamicListItem:last-of-type input") as HTMLElement).focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
public onAddItemKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
|
||||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
|
||||||
this.addItem();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class for ko component registration
|
|
||||||
*/
|
|
||||||
export const DynamicListComponent = {
|
|
||||||
viewModel: DynamicListViewModel,
|
|
||||||
template,
|
|
||||||
};
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<div class="dynamicList" data-bind="setTemplateReady: true">
|
|
||||||
<div class="dynamicListContainer" data-bind="foreach: listItems">
|
|
||||||
<div class="dynamicListItem">
|
|
||||||
<input
|
|
||||||
id="uniqueKeyItems"
|
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
data-bind="value: value, attr: {placeholder: $parent.placeholder, 'aria-label': $parent.ariaLabel}"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
class="dynamicListItemDelete"
|
|
||||||
title="Remove item"
|
|
||||||
role="button"
|
|
||||||
aria-label="Remove item"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="click: $parent.removeItem, event: { keydown: $parent.onRemoveItemKeyPress }"
|
|
||||||
>
|
|
||||||
<img src="/delete.svg" alt="Remove item" />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="dynamicListItemNew">
|
|
||||||
<span
|
|
||||||
class="dynamicListItemAdd"
|
|
||||||
id="addUniqueKeyBtn"
|
|
||||||
role="button"
|
|
||||||
aria-label="Add unique key"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="click: addItem, event: { keydown: onAddItemKeyPress }"
|
|
||||||
>
|
|
||||||
<img src="/Add-property.svg" data-bind="attr: {alt: buttonText}" /> <span data-bind="text: buttonText"></span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -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,8 +38,6 @@ describe("SettingsComponent", () => {
|
|||||||
title: "Scale & Settings",
|
title: "Scale & Settings",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
node: undefined,
|
node: undefined,
|
||||||
hashLocation: "settings",
|
|
||||||
onUpdateTabsButtons: undefined,
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -128,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,9 @@ 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 { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
|
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
|
||||||
import "./SettingsComponent.less";
|
import "./SettingsComponent.less";
|
||||||
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
|
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
|
||||||
@@ -109,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> {
|
||||||
@@ -121,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;
|
||||||
@@ -133,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";
|
||||||
@@ -145,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,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 = {
|
||||||
@@ -217,18 +217,19 @@ 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();
|
||||||
this.setBaseline();
|
this.setBaseline();
|
||||||
if (this.props.settingsTab.isActive()) {
|
if (this.props.settingsTab.isActive()) {
|
||||||
this.props.settingsTab.getContainer().onUpdateTabsButtons(this.getTabsButtons());
|
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(): void {
|
componentDidUpdate(): void {
|
||||||
if (this.props.settingsTab.isActive()) {
|
if (this.props.settingsTab.isActive()) {
|
||||||
this.props.settingsTab.getContainer().onUpdateTabsButtons(this.getTabsButtons());
|
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,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 &&
|
||||||
@@ -371,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) => {
|
||||||
@@ -883,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,
|
||||||
@@ -909,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,
|
||||||
@@ -964,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} /> : <></>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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: () => {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ import { ThroughputInput } from "./ThroughputInput";
|
|||||||
const props = {
|
const props = {
|
||||||
isDatabase: false,
|
isDatabase: false,
|
||||||
showFreeTierExceedThroughputTooltip: true,
|
showFreeTierExceedThroughputTooltip: true,
|
||||||
isSharded: false,
|
isSharded: true,
|
||||||
setThroughputValue: () => jest.fn(),
|
setThroughputValue: () => jest.fn(),
|
||||||
setIsAutoscale: () => jest.fn(),
|
setIsAutoscale: () => jest.fn(),
|
||||||
onCostAcknowledgeChange: () => jest.fn(),
|
onCostAcknowledgeChange: () => jest.fn(),
|
||||||
|
|||||||
@@ -41,9 +41,16 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
throughputHeaderText = AutoPilotUtils.getAutoPilotHeaderText().toLocaleLowerCase();
|
throughputHeaderText = AutoPilotUtils.getAutoPilotHeaderText().toLocaleLowerCase();
|
||||||
} else {
|
} else {
|
||||||
const minRU: string = SharedConstants.CollectionCreation.DefaultCollectionRUs400.toLocaleString();
|
const minRU: string = SharedConstants.CollectionCreation.DefaultCollectionRUs400.toLocaleString();
|
||||||
const maxRU: string = userContext.isTryCosmosDBSubscription
|
|
||||||
? Constants.TryCosmosExperience.maxRU.toLocaleString()
|
let maxRU: string;
|
||||||
: "unlimited";
|
if (userContext.isTryCosmosDBSubscription) {
|
||||||
|
maxRU = Constants.TryCosmosExperience.maxRU.toLocaleString();
|
||||||
|
} else if (!isSharded) {
|
||||||
|
maxRU = "10000";
|
||||||
|
} else {
|
||||||
|
maxRU = "unlimited";
|
||||||
|
}
|
||||||
|
|
||||||
throughputHeaderText = `throughput (${minRU} - ${maxRU} RU/s)`;
|
throughputHeaderText = `throughput (${minRU} - ${maxRU} RU/s)`;
|
||||||
}
|
}
|
||||||
return `${isDatabase ? "Database" : getCollectionName()} ${throughputHeaderText}`;
|
return `${isDatabase ? "Database" : getCollectionName()} ${throughputHeaderText}`;
|
||||||
|
|||||||
@@ -1,308 +0,0 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
import { KeyCodes } from "../../../Common/Constants";
|
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
|
||||||
import { FreeTierLimits } from "../../../Shared/Constants";
|
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
|
||||||
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
|
|
||||||
import ThroughputInputComponentAutoscaleV3 from "./ThroughputInputComponentAutoscaleV3.html";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Throughput Input:
|
|
||||||
*
|
|
||||||
* Creates a set of controls to input, sanitize and increase/decrease throughput
|
|
||||||
*
|
|
||||||
* How to use in your markup:
|
|
||||||
* <throughput-input params="{ value: anObservableToHoldTheValue, minimum: anObservableWithMinimum, maximum: anObservableWithMaximum }">
|
|
||||||
* </throughput-input>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parameters for this component
|
|
||||||
*/
|
|
||||||
export interface ThroughputInputParams {
|
|
||||||
/**
|
|
||||||
* Callback triggered when the template is bound to the component (for testing purposes)
|
|
||||||
*/
|
|
||||||
onTemplateReady?: () => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Observable to bind the Throughput value to
|
|
||||||
*/
|
|
||||||
value: ViewModels.Editable<number>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Text to use as id for testing
|
|
||||||
*/
|
|
||||||
testId: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Text to use as aria-label
|
|
||||||
*/
|
|
||||||
ariaLabel?: ko.Observable<string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum value in the range
|
|
||||||
*/
|
|
||||||
minimum: ko.Observable<number>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum value in the range
|
|
||||||
*/
|
|
||||||
maximum: ko.Observable<number>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Step value for increase/decrease
|
|
||||||
*/
|
|
||||||
step?: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Observable to bind the Throughput enabled status
|
|
||||||
*/
|
|
||||||
isEnabled?: ko.Observable<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should show pricing controls
|
|
||||||
*/
|
|
||||||
costsVisible: ko.Observable<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RU price
|
|
||||||
*/
|
|
||||||
requestUnitsUsageCost: ko.Computed<string>; // Our code assigns to ko.Computed, but unit test assigns to ko.Observable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* State of the spending acknowledge checkbox
|
|
||||||
*/
|
|
||||||
spendAckChecked?: ko.Observable<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* id of the spending acknowledge checkbox
|
|
||||||
*/
|
|
||||||
spendAckId?: ko.Observable<string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* spending acknowledge text
|
|
||||||
*/
|
|
||||||
spendAckText?: ko.Observable<string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show spending acknowledge controls
|
|
||||||
*/
|
|
||||||
spendAckVisible?: ko.Observable<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display * to the left of the label
|
|
||||||
*/
|
|
||||||
showAsMandatory: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If true, it will display a text to prompt users to use unlimited collections to go beyond max for fixed
|
|
||||||
*/
|
|
||||||
isFixed: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Label of the provisioned throughut control
|
|
||||||
*/
|
|
||||||
label: ko.Observable<string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Text of the info bubble for provisioned throughut control
|
|
||||||
*/
|
|
||||||
infoBubbleText?: ko.Observable<string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computed value that decides if value can exceed maximum allowable value
|
|
||||||
*/
|
|
||||||
canExceedMaximumValue?: ko.Computed<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CSS classes to apply on input element
|
|
||||||
*/
|
|
||||||
cssClass?: string;
|
|
||||||
|
|
||||||
isAutoPilotSelected: ko.Observable<boolean>;
|
|
||||||
throughputAutoPilotRadioId: string;
|
|
||||||
throughputProvisionedRadioId: string;
|
|
||||||
throughputModeRadioName: string;
|
|
||||||
maxAutoPilotThroughputSet: ViewModels.Editable<number>;
|
|
||||||
autoPilotUsageCost: ko.Computed<string>;
|
|
||||||
overrideWithAutoPilotSettings: ko.Observable<boolean>;
|
|
||||||
overrideWithProvisionedThroughputSettings: ko.Observable<boolean>;
|
|
||||||
freeTierExceedThroughputTooltip?: ko.Observable<string>;
|
|
||||||
freeTierExceedThroughputWarning?: ko.Observable<string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ThroughputInputViewModel extends WaitsForTemplateViewModel {
|
|
||||||
public ariaLabel: ko.Observable<string>;
|
|
||||||
public canExceedMaximumValue: ko.Computed<boolean>;
|
|
||||||
public step: ko.Computed<number>;
|
|
||||||
public testId: string;
|
|
||||||
public value: ViewModels.Editable<number>;
|
|
||||||
public minimum: ko.Observable<number>;
|
|
||||||
public maximum: ko.Observable<number>;
|
|
||||||
public isEnabled: ko.Observable<boolean>;
|
|
||||||
public cssClass: string;
|
|
||||||
public decreaseButtonAriaLabel: string;
|
|
||||||
public increaseButtonAriaLabel: string;
|
|
||||||
public costsVisible: ko.Observable<boolean>;
|
|
||||||
public requestUnitsUsageCost: ko.Computed<string>;
|
|
||||||
public spendAckChecked: ko.Observable<boolean>;
|
|
||||||
public spendAckId: ko.Observable<string>;
|
|
||||||
public spendAckText: ko.Observable<string>;
|
|
||||||
public spendAckVisible: ko.Observable<boolean>;
|
|
||||||
public showAsMandatory: boolean;
|
|
||||||
public infoBubbleText: string | ko.Observable<string>;
|
|
||||||
public label: ko.Observable<string>;
|
|
||||||
public isFixed: boolean;
|
|
||||||
public isAutoPilotSelected: ko.Observable<boolean>;
|
|
||||||
public throughputAutoPilotRadioId: string;
|
|
||||||
public throughputProvisionedRadioId: string;
|
|
||||||
public throughputModeRadioName: string;
|
|
||||||
public maxAutoPilotThroughputSet: ko.Observable<number>;
|
|
||||||
public autoPilotUsageCost: ko.Computed<string>;
|
|
||||||
public minAutoPilotThroughput: ko.Observable<number>;
|
|
||||||
public overrideWithAutoPilotSettings: ko.Observable<boolean>;
|
|
||||||
public overrideWithProvisionedThroughputSettings: ko.Observable<boolean>;
|
|
||||||
public isManualThroughputInputFieldRequired: ko.Computed<boolean>;
|
|
||||||
public isAutoscaleThroughputInputFieldRequired: ko.Computed<boolean>;
|
|
||||||
public freeTierExceedThroughputTooltip: ko.Observable<string>;
|
|
||||||
public freeTierExceedThroughputWarning: ko.Observable<string>;
|
|
||||||
public showFreeTierExceedThroughputTooltip: ko.Computed<boolean>;
|
|
||||||
public showFreeTierExceedThroughputWarning: ko.Computed<boolean>;
|
|
||||||
|
|
||||||
public constructor(options: ThroughputInputParams) {
|
|
||||||
super();
|
|
||||||
super.onTemplateReady((isTemplateReady: boolean) => {
|
|
||||||
if (isTemplateReady && options.onTemplateReady) {
|
|
||||||
options.onTemplateReady();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const params: ThroughputInputParams = options;
|
|
||||||
this.testId = params.testId || "ThroughputValue";
|
|
||||||
this.ariaLabel = ko.observable((params.ariaLabel && params.ariaLabel()) || "");
|
|
||||||
this.canExceedMaximumValue = params.canExceedMaximumValue || ko.computed(() => false);
|
|
||||||
this.isEnabled = params.isEnabled || ko.observable(true);
|
|
||||||
this.cssClass = params.cssClass || "textfontclr collid migration";
|
|
||||||
this.minimum = params.minimum;
|
|
||||||
this.maximum = params.maximum;
|
|
||||||
this.value = params.value;
|
|
||||||
this.costsVisible = options.costsVisible;
|
|
||||||
this.requestUnitsUsageCost = options.requestUnitsUsageCost;
|
|
||||||
this.spendAckChecked = options.spendAckChecked || ko.observable<boolean>(false);
|
|
||||||
this.spendAckId = options.spendAckId || ko.observable<string>();
|
|
||||||
this.spendAckText = options.spendAckText || ko.observable<string>();
|
|
||||||
this.spendAckVisible = options.spendAckVisible || ko.observable<boolean>(false);
|
|
||||||
this.showAsMandatory = !!options.showAsMandatory;
|
|
||||||
this.isFixed = !!options.isFixed;
|
|
||||||
this.infoBubbleText = options.infoBubbleText || ko.observable<string>();
|
|
||||||
this.label = options.label || ko.observable<string>();
|
|
||||||
this.isAutoPilotSelected = options.isAutoPilotSelected || ko.observable<boolean>(false);
|
|
||||||
this.isAutoPilotSelected.subscribe((value) => {
|
|
||||||
TelemetryProcessor.trace(Action.ToggleAutoscaleSetting, ActionModifiers.Mark, {
|
|
||||||
changedSelectedValueTo: value ? ActionModifiers.ToggleAutoscaleOn : ActionModifiers.ToggleAutoscaleOff,
|
|
||||||
dataExplorerArea: "Scale Tab V1",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.throughputAutoPilotRadioId = options.throughputAutoPilotRadioId;
|
|
||||||
this.throughputProvisionedRadioId = options.throughputProvisionedRadioId;
|
|
||||||
this.throughputModeRadioName = options.throughputModeRadioName;
|
|
||||||
this.overrideWithAutoPilotSettings = options.overrideWithAutoPilotSettings || ko.observable<boolean>(false);
|
|
||||||
this.overrideWithProvisionedThroughputSettings =
|
|
||||||
options.overrideWithProvisionedThroughputSettings || ko.observable<boolean>(false);
|
|
||||||
|
|
||||||
this.maxAutoPilotThroughputSet =
|
|
||||||
options.maxAutoPilotThroughputSet || ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput);
|
|
||||||
this.autoPilotUsageCost = options.autoPilotUsageCost;
|
|
||||||
this.minAutoPilotThroughput = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput);
|
|
||||||
|
|
||||||
this.step = ko.pureComputed(() => {
|
|
||||||
if (this.isAutoPilotSelected()) {
|
|
||||||
return AutoPilotUtils.autoPilotIncrementStep;
|
|
||||||
}
|
|
||||||
return params.step || ThroughputInputViewModel._defaultStep;
|
|
||||||
});
|
|
||||||
this.decreaseButtonAriaLabel = "Decrease throughput by " + this.step().toString();
|
|
||||||
this.increaseButtonAriaLabel = "Increase throughput by " + this.step().toString();
|
|
||||||
this.isManualThroughputInputFieldRequired = ko.pureComputed(() => this.isEnabled() && !this.isAutoPilotSelected());
|
|
||||||
this.isAutoscaleThroughputInputFieldRequired = ko.pureComputed(
|
|
||||||
() => this.isEnabled() && this.isAutoPilotSelected()
|
|
||||||
);
|
|
||||||
|
|
||||||
this.freeTierExceedThroughputTooltip = options.freeTierExceedThroughputTooltip || ko.observable<string>();
|
|
||||||
this.freeTierExceedThroughputWarning = options.freeTierExceedThroughputWarning || ko.observable<string>();
|
|
||||||
this.showFreeTierExceedThroughputTooltip = ko.pureComputed<boolean>(
|
|
||||||
() => !!this.freeTierExceedThroughputTooltip() && this.value() > FreeTierLimits.RU
|
|
||||||
);
|
|
||||||
|
|
||||||
this.showFreeTierExceedThroughputWarning = ko.pureComputed<boolean>(
|
|
||||||
() => !!this.freeTierExceedThroughputWarning() && this.value() > FreeTierLimits.RU
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public decreaseThroughput() {
|
|
||||||
let offerThroughput: number = this._getSanitizedValue();
|
|
||||||
|
|
||||||
if (offerThroughput > this.minimum()) {
|
|
||||||
offerThroughput -= this.step();
|
|
||||||
if (offerThroughput < this.minimum()) {
|
|
||||||
offerThroughput = this.minimum();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.value(offerThroughput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public increaseThroughput() {
|
|
||||||
let offerThroughput: number = this._getSanitizedValue();
|
|
||||||
|
|
||||||
if (offerThroughput < this.maximum() || this.canExceedMaximumValue()) {
|
|
||||||
offerThroughput += this.step();
|
|
||||||
if (offerThroughput > this.maximum() && !this.canExceedMaximumValue()) {
|
|
||||||
offerThroughput = this.maximum();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.value(offerThroughput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public onIncreaseKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
|
||||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
|
||||||
this.increaseThroughput();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
public onDecreaseKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
|
||||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
|
||||||
this.decreaseThroughput();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
private _getSanitizedValue(): number {
|
|
||||||
let throughput = this.value();
|
|
||||||
|
|
||||||
if (this.isAutoPilotSelected()) {
|
|
||||||
throughput = this.maxAutoPilotThroughputSet();
|
|
||||||
}
|
|
||||||
return isNaN(throughput) ? 0 : Number(throughput);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static _defaultStep: number = 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ThroughputInputComponentAutoPilotV3 = {
|
|
||||||
viewModel: ThroughputInputViewModel,
|
|
||||||
template: ThroughputInputComponentAutoscaleV3,
|
|
||||||
};
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
<div>
|
|
||||||
<div>
|
|
||||||
<p class="pkPadding">
|
|
||||||
<!-- ko if: showAsMandatory -->
|
|
||||||
<span class="mandatoryStar">*</span>
|
|
||||||
<!-- /ko -->
|
|
||||||
|
|
||||||
<span data-bind="text: label"></span>
|
|
||||||
|
|
||||||
<!-- ko if: infoBubbleText -->
|
|
||||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="../../../../images/info-bubble.svg" alt="More information" />
|
|
||||||
<span data-bind="text: infoBubbleText" class="tooltiptext throughputRuInfo"></span>
|
|
||||||
</span>
|
|
||||||
<!-- /ko -->
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ko if: !isFixed -->
|
|
||||||
<div class="throughputModeContainer">
|
|
||||||
<input
|
|
||||||
class="throughputModeRadio"
|
|
||||||
aria-label="Autopilot mode"
|
|
||||||
type="radio"
|
|
||||||
role="radio"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="
|
|
||||||
checked: isAutoPilotSelected,
|
|
||||||
checkedValue: true,
|
|
||||||
attr: {
|
|
||||||
id: throughputAutoPilotRadioId,
|
|
||||||
name: throughputModeRadioName,
|
|
||||||
'aria-checked': isAutoPilotSelected() ? 'true' : 'false'
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
class="throughputModeSpace"
|
|
||||||
data-bind="
|
|
||||||
attr: {
|
|
||||||
for: throughputAutoPilotRadioId
|
|
||||||
}"
|
|
||||||
>Autoscale
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<input
|
|
||||||
class="throughputModeRadio nonFirstRadio"
|
|
||||||
aria-label="Manual mode"
|
|
||||||
type="radio"
|
|
||||||
role="radio"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="
|
|
||||||
checked: isAutoPilotSelected,
|
|
||||||
checkedValue: false,
|
|
||||||
attr: {
|
|
||||||
id: throughputProvisionedRadioId,
|
|
||||||
name: throughputModeRadioName,
|
|
||||||
'aria-checked': !isAutoPilotSelected() ? 'true' : 'false'
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
class="throughputModeSpace"
|
|
||||||
data-bind="attr: {
|
|
||||||
for: throughputProvisionedRadioId
|
|
||||||
}"
|
|
||||||
>Manual
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<!-- /ko -->
|
|
||||||
|
|
||||||
<div data-bind="visible: isAutoPilotSelected">
|
|
||||||
<p>
|
|
||||||
<span
|
|
||||||
>Provision maximum RU/s required by this resource. Estimate your required RU/s with
|
|
||||||
<a target="_blank" href="https://cosmos.azure.com/capacitycalculator/">capacity calculator</a>.</span
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<span>Max RU/s</span>
|
|
||||||
</p>
|
|
||||||
<div data-bind="setTemplateReady: true">
|
|
||||||
<input
|
|
||||||
data-bind="textInput: overrideWithProvisionedThroughputSettings() ? '' : maxAutoPilotThroughputSet, attr:{
|
|
||||||
disabled: overrideWithProvisionedThroughputSettings(),
|
|
||||||
step: step,
|
|
||||||
'class':'migration collid select-font-size',
|
|
||||||
min: minAutoPilotThroughput,
|
|
||||||
'aria-label': 'Max request units per second',
|
|
||||||
type: isAutoscaleThroughputInputFieldRequired() ? 'number' : 'hidden',
|
|
||||||
css: {
|
|
||||||
dirty: maxAutoPilotThroughputSet.editableIsDirty
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p data-bind="visible: overrideWithProvisionedThroughputSettings && !overrideWithProvisionedThroughputSettings()">
|
|
||||||
<span
|
|
||||||
data-bind="
|
|
||||||
html: autoPilotUsageCost"
|
|
||||||
></span>
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
data-bind="visible: costsVisible && overrideWithProvisionedThroughputSettings && !overrideWithProvisionedThroughputSettings()"
|
|
||||||
>
|
|
||||||
<span data-bind="html: requestUnitsUsageCost"></span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- ko if: spendAckVisible -->
|
|
||||||
<p class="pkPadding">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
aria-label="acknowledge spend throughput"
|
|
||||||
data-bind="
|
|
||||||
attr: {
|
|
||||||
title: spendAckText,
|
|
||||||
id: spendAckId
|
|
||||||
},
|
|
||||||
checked: spendAckChecked"
|
|
||||||
/>
|
|
||||||
<span data-bind="text: spendAckText, attr: { for: spendAckId }"></span>
|
|
||||||
</p>
|
|
||||||
<!-- /ko -->
|
|
||||||
|
|
||||||
<!-- ko if: isFixed -->
|
|
||||||
<p>Choose unlimited storage capacity for more than 10,000 RU/s.</p>
|
|
||||||
<!-- /ko -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div data-bind="visible: !isAutoPilotSelected()">
|
|
||||||
<p>
|
|
||||||
<span
|
|
||||||
>Estimate your required throughput with
|
|
||||||
<a target="_blank" href="https://cosmos.azure.com/capacitycalculator/">capacity calculator</a></span
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="inputTooltip">
|
|
||||||
<span
|
|
||||||
data-bind="text: freeTierExceedThroughputTooltip, visible: showFreeTierExceedThroughputTooltip"
|
|
||||||
class="inputTooltipText"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div data-bind="setTemplateReady: true">
|
|
||||||
<input
|
|
||||||
data-bind="
|
|
||||||
textInput: overrideWithAutoPilotSettings() ? maxAutoPilotThroughputSet : value,
|
|
||||||
css: {
|
|
||||||
dirty: value.editableIsDirty
|
|
||||||
},
|
|
||||||
enable: isEnabled,
|
|
||||||
attr:{
|
|
||||||
type: isManualThroughputInputFieldRequired() ? 'number' : 'hidden',
|
|
||||||
'data-test': testId,
|
|
||||||
'class': cssClass,
|
|
||||||
step: step,
|
|
||||||
min: minimum,
|
|
||||||
max: canExceedMaximumValue() ? null : maximum,
|
|
||||||
'aria-label': ariaLabel,
|
|
||||||
disabled: overrideWithAutoPilotSettings(),
|
|
||||||
required: isManualThroughputInputFieldRequired()
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="freeTierInlineWarning" data-bind="visible: showFreeTierExceedThroughputWarning">
|
|
||||||
<span class="freeTierWarningIcon"><img src="/warning.svg" alt="Warning" /></span>
|
|
||||||
<span class="freeTierWarningMessage" data-bind="text: freeTierExceedThroughputWarning"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p data-bind="visible: costsVisible">
|
|
||||||
<span data-bind="html: requestUnitsUsageCost"></span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- ko if: spendAckVisible -->
|
|
||||||
<p class="pkPadding">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
aria-label="acknowledge spend throughput"
|
|
||||||
data-bind="
|
|
||||||
attr: {
|
|
||||||
title: spendAckText,
|
|
||||||
id: spendAckId
|
|
||||||
},
|
|
||||||
checked: spendAckChecked"
|
|
||||||
/>
|
|
||||||
<span data-bind="text: spendAckText, attr: { for: spendAckId }"></span>
|
|
||||||
</p>
|
|
||||||
<!-- /ko -->
|
|
||||||
|
|
||||||
<!-- ko if: isFixed -->
|
|
||||||
<p>Choose unlimited storage capacity for more than 10,000 RU/s.</p>
|
|
||||||
<!-- /ko -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
exports[`ThroughputInput Pane should render Default properly 1`] = `
|
exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
isDatabase={false}
|
isDatabase={false}
|
||||||
isSharded={false}
|
isSharded={true}
|
||||||
onCostAcknowledgeChange={[Function]}
|
onCostAcknowledgeChange={[Function]}
|
||||||
setIsAutoscale={[Function]}
|
setIsAutoscale={[Function]}
|
||||||
setThroughputValue={[Function]}
|
setThroughputValue={[Function]}
|
||||||
|
|||||||
@@ -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,23 +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.canExceedMaximumValue = ko.computed<boolean>(() => false);
|
explorerStub = {
|
||||||
explorerStub.findDatabaseWithId = () => database;
|
refreshAllDatabases: () => {},
|
||||||
explorerStub.refreshAllDatabases = () => Q.resolve();
|
} as Explorer;
|
||||||
return explorerStub;
|
});
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(createDocument as jest.Mock).mockResolvedValue(undefined);
|
(createDocument as jest.Mock).mockResolvedValue(undefined);
|
||||||
@@ -60,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);
|
||||||
@@ -109,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: {
|
||||||
@@ -127,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: {
|
||||||
@@ -142,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: {
|
||||||
@@ -164,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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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'\\)";
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export interface GremlinSimpleClientParameters {
|
|||||||
password: string;
|
password: string;
|
||||||
successCallback: (result: Result) => void;
|
successCallback: (result: Result) => void;
|
||||||
progressCallback: (result: Result) => void;
|
progressCallback: (result: Result) => void;
|
||||||
failureCallback: (result: Result | null, error: string) => void;
|
failureCallback: (result: Result, error: string) => void;
|
||||||
infoCallback: (msg: string) => void;
|
infoCallback: (msg: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +62,7 @@ export class GremlinSimpleClient {
|
|||||||
private static readonly requestChargeHeader = "x-ms-request-charge";
|
private static readonly requestChargeHeader = "x-ms-request-charge";
|
||||||
|
|
||||||
public params: GremlinSimpleClientParameters;
|
public params: GremlinSimpleClientParameters;
|
||||||
|
private protocols: string | string[];
|
||||||
private ws: WebSocket;
|
private ws: WebSocket;
|
||||||
|
|
||||||
public requestsToSend: { [requestId: string]: GremlinRequestMessage };
|
public requestsToSend: { [requestId: string]: GremlinRequestMessage };
|
||||||
@@ -71,7 +72,6 @@ export class GremlinSimpleClient {
|
|||||||
this.params = params;
|
this.params = params;
|
||||||
this.pendingRequests = {};
|
this.pendingRequests = {};
|
||||||
this.requestsToSend = {};
|
this.requestsToSend = {};
|
||||||
this.ws = GremlinSimpleClient.createWebSocket(this.params.endpoint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public connect() {
|
public connect() {
|
||||||
@@ -117,7 +117,7 @@ export class GremlinSimpleClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public decodeMessage(msg: MessageEvent): GremlinResponseMessage | null {
|
public decodeMessage(msg: MessageEvent): GremlinResponseMessage {
|
||||||
if (msg.data === null) {
|
if (msg.data === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -280,7 +280,7 @@ export class GremlinSimpleClient {
|
|||||||
|
|
||||||
public static utf8ToB64(utf8Str: string) {
|
public static utf8ToB64(utf8Str: string) {
|
||||||
return btoa(
|
return btoa(
|
||||||
encodeURIComponent(utf8Str).replace(/%([0-9A-F]{2})/g, function (_match, p1) {
|
encodeURIComponent(utf8Str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
|
||||||
return String.fromCharCode(parseInt(p1, 16));
|
return String.fromCharCode(parseInt(p1, 16));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -305,7 +305,7 @@ export class GremlinSimpleClient {
|
|||||||
return binaryMessage;
|
return binaryMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onOpen(_event: any) {
|
private onOpen(event: any) {
|
||||||
this.executeRequestsToSend();
|
this.executeRequestsToSend();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -3,103 +3,71 @@
|
|||||||
* If the component signals a change through the callback passed in the properties, it must render the React component when appropriate
|
* 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.
|
* and update any knockout observables passed from the parent.
|
||||||
*/
|
*/
|
||||||
import { CommandBar, ICommandBarItemProps } from "@fluentui/react";
|
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
|
||||||
import * as ko from "knockout";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
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 { userContext } from "../../../UserContext";
|
|
||||||
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";
|
||||||
|
|
||||||
export class CommandBarComponentAdapter implements ReactAdapter {
|
interface Props {
|
||||||
public parameters: ko.Observable<number>;
|
container: Explorer;
|
||||||
public container: Explorer;
|
|
||||||
private tabsButtons: CommandButtonComponentProps[];
|
|
||||||
private isNotebookTabActive: ko.Computed<boolean>;
|
|
||||||
|
|
||||||
constructor(container: Explorer) {
|
|
||||||
this.container = container;
|
|
||||||
this.tabsButtons = [];
|
|
||||||
this.isNotebookTabActive = ko.computed(
|
|
||||||
() => container.tabsManager.activeTab()?.tabKind === ViewModels.CollectionTabKind.NotebookV2
|
|
||||||
);
|
|
||||||
|
|
||||||
// These are the parameters watched by the react binding that will trigger a renderComponent() if one of the ko mutates
|
|
||||||
const toWatch = [
|
|
||||||
container.deleteCollectionText,
|
|
||||||
container.deleteDatabaseText,
|
|
||||||
container.addCollectionText,
|
|
||||||
container.isDatabaseNodeOrNoneSelected,
|
|
||||||
container.isDatabaseNodeSelected,
|
|
||||||
container.isNoneSelected,
|
|
||||||
container.isResourceTokenCollectionNodeSelected,
|
|
||||||
container.isHostedDataExplorerEnabled,
|
|
||||||
container.isSynapseLinkUpdating,
|
|
||||||
userContext?.databaseAccount,
|
|
||||||
this.isNotebookTabActive,
|
|
||||||
container.isServerlessEnabled,
|
|
||||||
];
|
|
||||||
|
|
||||||
ko.computed(() => ko.toJSON(toWatch)).subscribe(() => this.triggerRender());
|
|
||||||
this.parameters = ko.observable(Date.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
public onUpdateTabsButtons(buttons: CommandButtonComponentProps[]): void {
|
|
||||||
this.tabsButtons = buttons;
|
|
||||||
this.triggerRender();
|
|
||||||
}
|
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
|
||||||
const backgroundColor = StyleConstants.BaseLight;
|
|
||||||
|
|
||||||
const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(this.container);
|
|
||||||
const contextButtons = (this.tabsButtons || []).concat(
|
|
||||||
CommandBarComponentButtonFactory.createContextCommandBarButtons(this.container)
|
|
||||||
);
|
|
||||||
const controlButtons = CommandBarComponentButtonFactory.createControlCommandBarButtons(this.container);
|
|
||||||
|
|
||||||
const uiFabricStaticButtons = CommandBarUtil.convertButton(staticButtons, backgroundColor);
|
|
||||||
if (this.tabsButtons && this.tabsButtons.length > 0) {
|
|
||||||
uiFabricStaticButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
|
||||||
}
|
|
||||||
|
|
||||||
const uiFabricTabsButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(contextButtons, backgroundColor);
|
|
||||||
|
|
||||||
if (uiFabricTabsButtons.length > 0) {
|
|
||||||
uiFabricStaticButtons.push(CommandBarUtil.createDivider("commandBarDivider"));
|
|
||||||
}
|
|
||||||
|
|
||||||
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
|
||||||
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
|
||||||
|
|
||||||
if (this.isNotebookTabActive()) {
|
|
||||||
uiFabricControlButtons.unshift(
|
|
||||||
CommandBarUtil.createMemoryTracker("memoryTracker", this.container.memoryUsageInfo)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<div className="commandBarContainer">
|
|
||||||
<CommandBar
|
|
||||||
ariaLabel="Use left and right arrow keys to navigate between commands"
|
|
||||||
items={uiFabricStaticButtons.concat(uiFabricTabsButtons)}
|
|
||||||
farItems={uiFabricControlButtons}
|
|
||||||
styles={{
|
|
||||||
root: { backgroundColor: backgroundColor },
|
|
||||||
}}
|
|
||||||
overflowButtonProps={{ ariaLabel: "More commands" }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private triggerRender() {
|
|
||||||
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CommandBarStore {
|
||||||
|
contextButtons: CommandButtonComponentProps[];
|
||||||
|
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCommandBar: UseStore<CommandBarStore> = create((set) => ({
|
||||||
|
contextButtons: [],
|
||||||
|
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
||||||
|
const selectedNodeState = useSelectedNode();
|
||||||
|
const buttons = useCommandBar((state) => state.contextButtons);
|
||||||
|
const backgroundColor = StyleConstants.BaseLight;
|
||||||
|
|
||||||
|
const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(container, selectedNodeState);
|
||||||
|
const contextButtons = (buttons || []).concat(
|
||||||
|
CommandBarComponentButtonFactory.createContextCommandBarButtons(container, selectedNodeState)
|
||||||
|
);
|
||||||
|
const controlButtons = CommandBarComponentButtonFactory.createControlCommandBarButtons(container);
|
||||||
|
|
||||||
|
const uiFabricStaticButtons = CommandBarUtil.convertButton(staticButtons, backgroundColor);
|
||||||
|
if (buttons && buttons.length > 0) {
|
||||||
|
uiFabricStaticButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
||||||
|
}
|
||||||
|
|
||||||
|
const uiFabricTabsButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(contextButtons, backgroundColor);
|
||||||
|
|
||||||
|
if (uiFabricTabsButtons.length > 0) {
|
||||||
|
uiFabricStaticButtons.push(CommandBarUtil.createDivider("commandBarDivider"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
||||||
|
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
||||||
|
|
||||||
|
if (container.tabsManager.activeTab()?.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
|
||||||
|
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker", container.memoryUsageInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="commandBarContainer">
|
||||||
|
<FluentCommandBar
|
||||||
|
ariaLabel="Use left and right arrow keys to navigate between commands"
|
||||||
|
items={uiFabricStaticButtons.concat(uiFabricTabsButtons)}
|
||||||
|
farItems={uiFabricControlButtons}
|
||||||
|
styles={{
|
||||||
|
root: { backgroundColor: backgroundColor },
|
||||||
|
}}
|
||||||
|
overflowButtonProps={{ ariaLabel: "More commands" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
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;
|
||||||
mockExplorer.addCollectionText = ko.observable("mockText");
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
properties: {
|
properties: {
|
||||||
@@ -23,19 +27,13 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
},
|
},
|
||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
});
|
});
|
||||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
|
||||||
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
|
||||||
);
|
);
|
||||||
@@ -43,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
|
||||||
);
|
);
|
||||||
@@ -55,11 +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;
|
||||||
mockExplorer.addCollectionText = ko.observable("mockText");
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
|
portalEnv: "prod",
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
properties: {
|
properties: {
|
||||||
capabilities: [{ name: "EnableTable" }],
|
capabilities: [{ name: "EnableTable" }],
|
||||||
@@ -67,19 +71,19 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
});
|
});
|
||||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
});
|
||||||
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();
|
||||||
});
|
});
|
||||||
@@ -87,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();
|
||||||
});
|
});
|
||||||
@@ -97,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);
|
||||||
@@ -109,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);
|
||||||
@@ -123,10 +127,10 @@ 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;
|
||||||
mockExplorer.addCollectionText = ko.observable("mockText");
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
properties: {
|
properties: {
|
||||||
@@ -134,11 +138,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
},
|
},
|
||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
});
|
});
|
||||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
|
||||||
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -154,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);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -162,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();
|
||||||
});
|
});
|
||||||
@@ -184,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();
|
||||||
});
|
});
|
||||||
@@ -192,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);
|
||||||
@@ -203,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);
|
||||||
@@ -215,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();
|
||||||
});
|
});
|
||||||
@@ -223,10 +225,10 @@ 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;
|
||||||
mockExplorer.addCollectionText = ko.observable("mockText");
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
properties: {
|
properties: {
|
||||||
@@ -235,10 +237,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
});
|
});
|
||||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
|
||||||
|
|
||||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
|
||||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -251,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", () => {
|
||||||
@@ -263,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();
|
||||||
});
|
});
|
||||||
@@ -285,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();
|
||||||
});
|
});
|
||||||
@@ -293,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);
|
||||||
@@ -304,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);
|
||||||
@@ -315,10 +314,10 @@ 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;
|
||||||
mockExplorer.addCollectionText = ko.observable("mockText");
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
properties: {
|
properties: {
|
||||||
@@ -328,13 +327,10 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
|
||||||
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(() => {
|
||||||
@@ -348,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();
|
||||||
});
|
});
|
||||||
@@ -357,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
|
||||||
);
|
);
|
||||||
@@ -365,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();
|
||||||
@@ -378,12 +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.addCollectionText = ko.observable("mockText");
|
mockExplorer.resourceTokenCollection = ko.observable(mockCollection);
|
||||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
|
||||||
mockExplorer.isResourceTokenCollectionNodeSelected = ko.computed(() => true);
|
|
||||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
authType: AuthType.ResourceToken,
|
authType: AuthType.ResourceToken,
|
||||||
});
|
});
|
||||||
@@ -395,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);
|
||||||
|
|||||||
@@ -21,17 +21,26 @@ import { AuthType } from "../../../AuthType";
|
|||||||
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 { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { 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 { 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);
|
||||||
@@ -65,35 +74,36 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
|
|||||||
buttons.push(createOpenTerminalButton(container));
|
buttons.push(createOpenTerminalButton(container));
|
||||||
|
|
||||||
buttons.push(createNotebookWorkspaceResetButton(container));
|
buttons.push(createNotebookWorkspaceResetButton(container));
|
||||||
if (
|
|
||||||
(userContext.apiType === "Mongo" && container.isShellEnabled() && container.isDatabaseNodeOrNoneSelected()) ||
|
buttons.push(createDivider());
|
||||||
userContext.apiType === "Cassandra"
|
buttons.push(createOpenPostgreSQLTerminalButton(container));
|
||||||
) {
|
|
||||||
buttons.push(createDivider());
|
if (userContext.apiType === "Mongo" &&
|
||||||
if (userContext.apiType === "Cassandra") {
|
container.isShellEnabled() &&
|
||||||
buttons.push(createOpenCassandraTerminalButton(container));
|
selectedNodeState.isDatabaseNodeOrNoneSelected()){
|
||||||
} else {
|
|
||||||
buttons.push(createOpenMongoTerminalButton(container));
|
buttons.push(createOpenMongoTerminalButton(container));
|
||||||
}
|
}
|
||||||
|
if (userContext.apiType === "Cassandra") {
|
||||||
|
buttons.push(createOpenCassandraTerminalButton(container));
|
||||||
}
|
}
|
||||||
} 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,16 +113,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,16 +130,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 {
|
||||||
@@ -139,7 +152,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);
|
||||||
}
|
}
|
||||||
@@ -152,7 +165,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",
|
||||||
@@ -161,19 +174,22 @@ 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,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
container.openSidePanel("Open Full Screen", <OpenFullScreen />);
|
useSidePanel.getState().openSidePanel("Open Full Screen", <OpenFullScreen />);
|
||||||
},
|
},
|
||||||
commandButtonLabel: undefined,
|
commandButtonLabel: undefined,
|
||||||
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);
|
||||||
@@ -215,7 +231,7 @@ function areScriptsSupported(): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createNewCollectionGroup(container: Explorer): CommandButtonComponentProps {
|
function createNewCollectionGroup(container: Explorer): CommandButtonComponentProps {
|
||||||
const label = container.addCollectionText();
|
const label = `New ${getCollectionName()}`;
|
||||||
return {
|
return {
|
||||||
iconSrc: AddCollectionIcon,
|
iconSrc: AddCollectionIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
@@ -232,7 +248,7 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container.isServerlessEnabled()) {
|
if (isServerlessAccount()) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,20 +287,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";
|
||||||
@@ -292,23 +308,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";
|
||||||
@@ -316,13 +333,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);
|
||||||
}
|
}
|
||||||
@@ -333,13 +350,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);
|
||||||
}
|
}
|
||||||
@@ -350,13 +367,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);
|
||||||
}
|
}
|
||||||
@@ -403,12 +420,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: () => container.openLoadQueryPanel(),
|
onCommandClick: () => useSidePanel.getState().openSidePanel("Load Query", <LoadQueryPane />),
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
hasPopup: true,
|
hasPopup: true,
|
||||||
@@ -450,6 +467,19 @@ function createOpenTerminalButton(container: Explorer): CommandButtonComponentPr
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createOpenPostgreSQLTerminalButton(container: Explorer): CommandButtonComponentProps {
|
||||||
|
const label = "Open PostgreSQL Shell";
|
||||||
|
return {
|
||||||
|
iconSrc: HostedTerminalIcon,
|
||||||
|
iconAlt: label,
|
||||||
|
onCommandClick: () => container.openNotebookTerminal(ViewModels.TerminalKind.PostgreSQL),
|
||||||
|
commandButtonLabel: label,
|
||||||
|
hasPopup: false,
|
||||||
|
disabled: false,
|
||||||
|
ariaLabel: label,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function createOpenMongoTerminalButton(container: Explorer): CommandButtonComponentProps {
|
function createOpenMongoTerminalButton(container: Explorer): CommandButtonComponentProps {
|
||||||
const label = "Open Mongo Shell";
|
const label = "Open Mongo Shell";
|
||||||
const tooltip =
|
const tooltip =
|
||||||
@@ -529,19 +559,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];
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { StyleConstants } from "../../../Common/Constants";
|
|||||||
import { MemoryUsageInfo } from "../../../Contracts/DataModels";
|
import { MemoryUsageInfo } from "../../../Contracts/DataModels";
|
||||||
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 { ArcadiaMenuPicker } from "../../Controls/Arcadia/ArcadiaMenuPicker";
|
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { MemoryTrackerComponent } from "./MemoryTrackerComponent";
|
import { MemoryTrackerComponent } from "./MemoryTrackerComponent";
|
||||||
|
|
||||||
@@ -168,10 +167,6 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (btn.isArcadiaPicker && btn.arcadiaProps) {
|
|
||||||
result.commandBarButtonAs = () => <ArcadiaMenuPicker {...btn.arcadiaProps} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
16
src/Explorer/Menus/NotificationConsole/ConsoleData.tsx
Normal file
16
src/Explorer/Menus/NotificationConsole/ConsoleData.tsx
Normal file
@@ -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 => {
|
||||||
|
|||||||
@@ -15,26 +15,9 @@ import LoadingIcon from "../../../../images/loading.svg";
|
|||||||
import ChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
import ChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
||||||
import ChevronUpIcon from "../../../../images/QueryBuilder/CollapseChevronUp_16x.png";
|
import ChevronUpIcon from "../../../../images/QueryBuilder/CollapseChevronUp_16x.png";
|
||||||
import { ClientDefaults, KeyCodes } from "../../../Common/Constants";
|
import { ClientDefaults, KeyCodes } from "../../../Common/Constants";
|
||||||
|
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;
|
||||||
@@ -321,3 +304,22 @@ const PrPreview = (props: { pr: string }) => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const NotificationConsole: React.FC = () => {
|
||||||
|
const setIsExpanded = useNotificationConsole((state) => state.setIsExpanded);
|
||||||
|
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
|
||||||
|
// This component only exists so we can use hooks and pass them down to a non-functional component
|
||||||
|
return (
|
||||||
|
<NotificationConsoleComponent
|
||||||
|
consoleData={consoleData}
|
||||||
|
inProgressConsoleDataIdToBeDeleted={inProgressConsoleDataIdToBeDeleted}
|
||||||
|
isConsoleExpanded={isExpanded}
|
||||||
|
setIsConsoleExpanded={setIsExpanded}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -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");
|
||||||
defineConfigOption({
|
const readOnlyValue = params.isReadOnly ? "nocursor" : undefined;
|
||||||
label: "Show Line numbers",
|
if (!readOnlyConfigOption) {
|
||||||
key: "monaco.lineNumbers",
|
defineConfigOption({
|
||||||
values: [
|
label: "Read-only",
|
||||||
{ label: "Yes", value: true },
|
key: "codeMirror.readOnly",
|
||||||
{ label: "No", value: false },
|
values: [
|
||||||
],
|
{ label: "Read-Only", value: "nocursor" },
|
||||||
defaultValue: true,
|
{ label: "Not read-only", value: undefined },
|
||||||
});
|
],
|
||||||
|
defaultValue: readOnlyValue,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.store.dispatch(readOnlyConfigOption.action(readOnlyValue));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
.notebookComponentContainer {
|
.notebookComponentContainer {
|
||||||
text-transform:none;
|
text-transform: none;
|
||||||
line-height:1.28581;
|
line-height: 1.28581;
|
||||||
letter-spacing:0;
|
letter-spacing: 0;
|
||||||
font-size:14px;
|
font-size: 14px;
|
||||||
font-weight:400;
|
font-weight: 400;
|
||||||
color:#182026;
|
color: #182026;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
.hotKeys {
|
.hotKeys {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
KernelRef,
|
KernelRef,
|
||||||
RemoteKernelProps,
|
RemoteKernelProps,
|
||||||
selectors,
|
selectors,
|
||||||
ServerConfig as JupyterServerConfig,
|
ServerConfig as JupyterServerConfig
|
||||||
} from "@nteract/core";
|
} from "@nteract/core";
|
||||||
import { Channels, childOf, createMessage, JupyterMessage, message, ofMessageType } from "@nteract/messaging";
|
import { Channels, childOf, createMessage, JupyterMessage, message, ofMessageType } from "@nteract/messaging";
|
||||||
import { RecordOf } from "immutable";
|
import { RecordOf } from "immutable";
|
||||||
@@ -29,14 +29,13 @@ import {
|
|||||||
switchMap,
|
switchMap,
|
||||||
take,
|
take,
|
||||||
tap,
|
tap,
|
||||||
timeout,
|
timeout
|
||||||
} from "rxjs/operators";
|
} from "rxjs/operators";
|
||||||
import { webSocket } from "rxjs/webSocket";
|
import { webSocket } from "rxjs/webSocket";
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import { Areas } from "../../../Common/Constants";
|
import { Areas } from "../../../Common/Constants";
|
||||||
import { Action as TelemetryAction, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action as TelemetryAction, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { decryptJWTToken } from "../../../Utils/AuthorizationUtils";
|
|
||||||
import { logConsoleError, logConsoleInfo } from "../../../Utils/NotificationConsoleUtils";
|
import { logConsoleError, logConsoleInfo } from "../../../Utils/NotificationConsoleUtils";
|
||||||
import * as FileSystemUtil from "../FileSystemUtil";
|
import * as FileSystemUtil from "../FileSystemUtil";
|
||||||
import * as cdbActions from "../NotebookComponent/actions";
|
import * as cdbActions from "../NotebookComponent/actions";
|
||||||
@@ -105,15 +104,10 @@ const formWebSocketURL = (serverConfig: NotebookServiceConfig, kernelId: string,
|
|||||||
params.append("session_id", sessionId);
|
params.append("session_id", sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = getUserPuid();
|
|
||||||
if (userId) {
|
|
||||||
params.append("user_id", userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const q = params.toString();
|
const q = params.toString();
|
||||||
const suffix = q !== "" ? `?${q}` : "";
|
const suffix = q !== "" ? `?${q}` : "";
|
||||||
|
|
||||||
const url = (serverConfig.endpoint || "") + `api/kernels/${kernelId}/channels${suffix}`;
|
const url = (serverConfig.endpoint.slice(0, -1) || "") + `api/kernels/${kernelId}/channels${suffix}`;
|
||||||
|
|
||||||
return url.replace(/^http(s)?/, "ws$1");
|
return url.replace(/^http(s)?/, "ws$1");
|
||||||
};
|
};
|
||||||
@@ -289,7 +283,6 @@ export const launchWebSocketKernelEpic = (
|
|||||||
return EMPTY;
|
return EMPTY;
|
||||||
}
|
}
|
||||||
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
|
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
|
||||||
serverConfig.userPuid = getUserPuid();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
payload: { kernelSpecName, cwd, kernelRef, contentRef },
|
payload: { kernelSpecName, cwd, kernelRef, contentRef },
|
||||||
@@ -766,25 +759,6 @@ const executeFocusedCellAndFocusNextEpic = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function getUserPuid(): string {
|
|
||||||
const arcadiaToken = window.dataExplorer && window.dataExplorer.arcadiaToken();
|
|
||||||
if (!arcadiaToken) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
let userPuid;
|
|
||||||
try {
|
|
||||||
const tokenPayload = decryptJWTToken(arcadiaToken);
|
|
||||||
if (tokenPayload && tokenPayload.hasOwnProperty("puid")) {
|
|
||||||
userPuid = tokenPayload.puid;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
return userPuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close tab if mimetype not supported
|
* Close tab if mimetype not supported
|
||||||
* @param action$
|
* @param action$
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export class NotebookContainerClient {
|
|||||||
|
|
||||||
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${notebookServerEndpoint}/api/metrics/memory`, {
|
const response = await fetch(`${notebookServerEndpoint}api/metrics/memory`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: authToken,
|
Authorization: authToken,
|
||||||
@@ -130,19 +130,23 @@ 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);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export class NotebookContentClient {
|
|||||||
private notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>,
|
private notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>,
|
||||||
public notebookBasePath: ko.Observable<string>,
|
public notebookBasePath: ko.Observable<string>,
|
||||||
private contentProvider: IContentProvider
|
private contentProvider: IContentProvider
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This updates the item and points all the children's parent to this item
|
* This updates the item and points all the children's parent to this item
|
||||||
@@ -25,6 +25,9 @@ export class NotebookContentClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sleep = (milliseconds: number) => {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, milliseconds))
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param parent parent folder
|
* @param parent parent folder
|
||||||
@@ -35,6 +38,7 @@ export class NotebookContentClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const type = "notebook";
|
const type = "notebook";
|
||||||
|
|
||||||
return this.contentProvider
|
return this.contentProvider
|
||||||
.create<"notebook">(this.getServerConfig(), parent.path, { type })
|
.create<"notebook">(this.getServerConfig(), parent.path, { type })
|
||||||
.toPromise()
|
.toPromise()
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ import { MemoryUsageInfo } from "../../Contracts/DataModels";
|
|||||||
import { GitHubClient } from "../../GitHub/GitHubClient";
|
import { GitHubClient } from "../../GitHub/GitHubClient";
|
||||||
import { GitHubContentProvider } from "../../GitHub/GitHubContentProvider";
|
import { GitHubContentProvider } from "../../GitHub/GitHubContentProvider";
|
||||||
import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService";
|
import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService";
|
||||||
|
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||||
import { JunoClient } from "../../Juno/JunoClient";
|
import { JunoClient } from "../../Juno/JunoClient";
|
||||||
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 { getFullName } from "../../Utils/UserUtils";
|
import { getFullName } from "../../Utils/UserUtils";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { ContextualPaneBase } from "../Panes/ContextualPaneBase";
|
|
||||||
import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane";
|
import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane";
|
||||||
import { PublishNotebookPane } from "../Panes/PublishNotebookPane/PublishNotebookPane";
|
import { PublishNotebookPane } from "../Panes/PublishNotebookPane/PublishNotebookPane";
|
||||||
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
|
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
|
||||||
@@ -56,8 +56,6 @@ export default class NotebookManager {
|
|||||||
public gitHubOAuthService: GitHubOAuthService;
|
public gitHubOAuthService: GitHubOAuthService;
|
||||||
public gitHubClient: GitHubClient;
|
public gitHubClient: GitHubClient;
|
||||||
|
|
||||||
public gitHubReposPane: ContextualPaneBase;
|
|
||||||
|
|
||||||
public initialize(params: NotebookManagerOptions): void {
|
public initialize(params: NotebookManagerOptions): void {
|
||||||
this.params = params;
|
this.params = params;
|
||||||
this.junoClient = new JunoClient();
|
this.junoClient = new JunoClient();
|
||||||
@@ -98,7 +96,7 @@ export default class NotebookManager {
|
|||||||
this.gitHubOAuthService.getTokenObservable().subscribe((token) => {
|
this.gitHubOAuthService.getTokenObservable().subscribe((token) => {
|
||||||
this.gitHubClient.setToken(token?.access_token);
|
this.gitHubClient.setToken(token?.access_token);
|
||||||
if (this?.gitHubOAuthService.isLoggedIn()) {
|
if (this?.gitHubOAuthService.isLoggedIn()) {
|
||||||
this.params.container.closeSidePanel();
|
useSidePanel.getState().closeSidePanel();
|
||||||
this.params.container.openGitHubReposPanel("Manager GitHub settings", this.junoClient);
|
this.params.container.openGitHubReposPanel("Manager GitHub settings", this.junoClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,37 +125,37 @@ export default class NotebookManager {
|
|||||||
onTakeSnapshot: (request: SnapshotRequest) => void,
|
onTakeSnapshot: (request: SnapshotRequest) => void,
|
||||||
onClosePanel: () => void
|
onClosePanel: () => void
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const explorer = this.params.container;
|
useSidePanel
|
||||||
explorer.openSidePanel(
|
.getState()
|
||||||
"Publish Notebook",
|
.openSidePanel(
|
||||||
<PublishNotebookPane
|
"Publish Notebook",
|
||||||
explorer={this.params.container}
|
<PublishNotebookPane
|
||||||
junoClient={this.junoClient}
|
explorer={this.params.container}
|
||||||
closePanel={this.params.container.closeSidePanel}
|
junoClient={this.junoClient}
|
||||||
openNotificationConsole={this.params.container.expandConsole}
|
name={name}
|
||||||
name={name}
|
author={getFullName()}
|
||||||
author={getFullName()}
|
notebookContent={content}
|
||||||
notebookContent={content}
|
notebookContentRef={notebookContentRef}
|
||||||
notebookContentRef={notebookContentRef}
|
onTakeSnapshot={onTakeSnapshot}
|
||||||
onTakeSnapshot={onTakeSnapshot}
|
/>,
|
||||||
/>,
|
onClosePanel
|
||||||
onClosePanel
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public openCopyNotebookPane(name: string, content: string): void {
|
public openCopyNotebookPane(name: string, content: string): void {
|
||||||
const { container } = this.params;
|
const { container } = this.params;
|
||||||
container.openSidePanel(
|
useSidePanel
|
||||||
"Copy Notebook",
|
.getState()
|
||||||
<CopyNotebookPane
|
.openSidePanel(
|
||||||
container={container}
|
"Copy Notebook",
|
||||||
closePanel={container.closeSidePanel}
|
<CopyNotebookPane
|
||||||
junoClient={this.junoClient}
|
container={container}
|
||||||
gitHubOAuthService={this.gitHubOAuthService}
|
junoClient={this.junoClient}
|
||||||
name={name}
|
gitHubOAuthService={this.gitHubOAuthService}
|
||||||
content={content}
|
name={name}
|
||||||
/>
|
content={content}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Octokit's error handler uses any
|
// Octokit's error handler uses any
|
||||||
|
|||||||
@@ -1,56 +1,68 @@
|
|||||||
.NotebookReadOnlyRender {
|
.NotebookReadOnlyRender {
|
||||||
.nteract-cell-container {
|
.nteract-cell-container {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nteract-cell {
|
.nteract-cell {
|
||||||
padding: 0.5px;
|
padding: 0.5px;
|
||||||
border: 1px solid #ffffff;
|
border: 1px solid #ffffff;
|
||||||
border-left: 3px solid #ffffff;
|
border-left: 3px solid #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror-scroll {
|
.CodeMirror-scroll {
|
||||||
background-color: #f5f5f5;
|
overflow: hidden !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror-lines {
|
.CodeMirror-lines {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nteract-cell:hover {
|
.CodeMirror {
|
||||||
border: 1px solid #0078d4;
|
height: inherit;
|
||||||
border-left: 3px solid #0078d4;
|
}
|
||||||
|
|
||||||
.CodeMirror-scroll {
|
.CodeMirror-scroll,
|
||||||
background-color: #ffffff;
|
.CodeMirror-linenumber,
|
||||||
}
|
.CodeMirror-gutters {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
.nteract-cell-outputs {
|
.nteract-cell:hover {
|
||||||
border-top: 1px solid #d7d7d7;
|
border: 1px solid #0078d4;
|
||||||
}
|
border-left: 3px solid #0078d4;
|
||||||
|
|
||||||
.nteract-md-cell {
|
.CodeMirror-scroll,
|
||||||
background-color: #ffffff;
|
.CodeMirror-linenumber,
|
||||||
}
|
.CodeMirror-gutters {
|
||||||
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nteract-cell-outputs {
|
.nteract-cell-outputs {
|
||||||
padding: 10px;
|
border-top: 1px solid #d7d7d7;
|
||||||
border-top: 1px solid #ffffff;
|
|
||||||
|
|
||||||
pre {
|
|
||||||
background-color: #ffffff;
|
|
||||||
border: none;
|
|
||||||
padding: 0px;
|
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nteract-md-cell {
|
.nteract-md-cell {
|
||||||
background-color: #f5f5f5;
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.nteract-cell:hover.nteract-md-cell {
|
.nteract-cell-outputs {
|
||||||
background-color: #ffffff;
|
padding: 10px;
|
||||||
|
border-top: 1px solid #ffffff;
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: none;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nteract-md-cell {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nteract-cell:hover.nteract-md-cell {
|
||||||
|
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>
|
||||||
|
|||||||
@@ -3,110 +3,122 @@
|
|||||||
@HighlightColor: #0078d4;
|
@HighlightColor: #0078d4;
|
||||||
|
|
||||||
.NotebookRendererContainer {
|
.NotebookRendererContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.NotebookRenderer {
|
.NotebookRenderer {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
||||||
.nteract-cells {
|
.nteract-cells {
|
||||||
padding-top: 0px;
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nteract-cell-container {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.nteract-cell {
|
||||||
|
padding: 0.5px;
|
||||||
|
border: 1px solid #ffffff;
|
||||||
|
border-left: 3px solid #ffffff;
|
||||||
|
|
||||||
|
.CellContextMenuButton {
|
||||||
|
position: sticky;
|
||||||
|
z-index: 1;
|
||||||
|
top: 0px;
|
||||||
|
right: 0px;
|
||||||
|
margin: 0px 0px 0px -100%;
|
||||||
|
float: right;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nteract-cell-container {
|
.CodeMirror-scroll {
|
||||||
margin-bottom: 10px;
|
overflow: hidden !important;
|
||||||
|
|
||||||
.nteract-cell {
|
|
||||||
padding: 0.5px;
|
|
||||||
border: 1px solid #ffffff;
|
|
||||||
border-left: 3px solid #ffffff;
|
|
||||||
|
|
||||||
.CellContextMenuButton {
|
|
||||||
position: sticky;
|
|
||||||
z-index: 1;
|
|
||||||
top: 0px;
|
|
||||||
right: 0px;
|
|
||||||
margin: 0px 0px 0px -100%;
|
|
||||||
float: right;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-scroll, .CodeMirror-linenumber, .CodeMirror-gutters {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nteract-cell:hover {
|
|
||||||
border: 1px solid @HoverColor;
|
|
||||||
border-left: 3px solid @HoverColor;
|
|
||||||
|
|
||||||
.CellContextMenuButton {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nteract-cell-container.selected {
|
.CodeMirror-scroll,
|
||||||
.nteract-cell {
|
.CodeMirror-linenumber,
|
||||||
border: 1px solid @HighlightColor;
|
.CodeMirror-gutters {
|
||||||
border-left: 3px solid @HighlightColor;
|
background-color: #f5f5f5;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// White background when hovered or selected
|
.CodeMirror {
|
||||||
.nteract-cell:hover, .nteract-cell-container.selected .nteract-cell {
|
height: inherit;
|
||||||
.CodeMirror-scroll, .CodeMirror-linenumber, .CodeMirror-gutters {
|
}
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-linenumber {
|
.nteract-cell:hover {
|
||||||
color: #015CDA;
|
border: 1px solid @HoverColor;
|
||||||
}
|
border-left: 3px solid @HoverColor;
|
||||||
|
|
||||||
.nteract-cell-outputs {
|
.CellContextMenuButton {
|
||||||
border-top: 1px solid @HoverColor;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.nteract-md-cell {
|
.nteract-cell-container.selected {
|
||||||
background-color: #ffffff;
|
.nteract-cell {
|
||||||
}
|
border: 1px solid @HighlightColor;
|
||||||
|
border-left: 3px solid @HighlightColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// White background when hovered or selected
|
||||||
|
.nteract-cell:hover,
|
||||||
|
.nteract-cell-container.selected .nteract-cell {
|
||||||
|
.CodeMirror-scroll,
|
||||||
|
.CodeMirror-linenumber,
|
||||||
|
.CodeMirror-gutters {
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-linenumber {
|
||||||
|
color: #015cda;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nteract-cell-outputs {
|
.nteract-cell-outputs {
|
||||||
padding: 10px;
|
border-top: 1px solid @HoverColor;
|
||||||
border-top: 1px solid #ffffff;
|
|
||||||
|
|
||||||
pre {
|
|
||||||
background-color: #ffffff;
|
|
||||||
border: none;
|
|
||||||
padding: 0px;
|
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nteract-md-cell {
|
.nteract-md-cell {
|
||||||
background-color: #f5f5f5;
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.nteract-cell:hover.nteract-md-cell {
|
.nteract-cell-outputs {
|
||||||
background-color: #ffffff;
|
padding: 10px;
|
||||||
}
|
border-top: 1px solid #ffffff;
|
||||||
|
|
||||||
.nteract-md-cell .ntreact-cell-source {
|
pre {
|
||||||
width: 100%;
|
background-color: #ffffff;
|
||||||
|
border: none;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nteract-md-cell {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nteract-cell:hover.nteract-md-cell {
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nteract-md-cell .ntreact-cell-source {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Undo tree.less
|
// Undo tree.less
|
||||||
.expanded::before {
|
.expanded::before {
|
||||||
content: '';
|
content: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
.monaco-editor .monaco-list .main {
|
.monaco-editor .monaco-list .main {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
query = query.concat(" OR");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return query;
|
||||||
}
|
}
|
||||||
|
return "SELECT * FROM c";
|
||||||
if (
|
|
||||||
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 false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
@@ -20,6 +20,7 @@ import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"
|
|||||||
import { configContext, Platform } from "../../ConfigContext";
|
import { configContext, Platform } from "../../ConfigContext";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { SubscriptionType } from "../../Contracts/SubscriptionType";
|
import { SubscriptionType } from "../../Contracts/SubscriptionType";
|
||||||
|
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||||
import { CollectionCreation } from "../../Shared/Constants";
|
import { CollectionCreation } from "../../Shared/Constants";
|
||||||
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";
|
||||||
@@ -30,14 +31,13 @@ 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";
|
||||||
|
|
||||||
export interface AddCollectionPanelProps {
|
export interface AddCollectionPanelProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
closePanel: () => void;
|
|
||||||
openNotificationConsole: () => void;
|
|
||||||
databaseId?: string;
|
databaseId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,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 && (
|
||||||
@@ -133,16 +135,14 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
message={this.state.errorMessage}
|
message={this.state.errorMessage}
|
||||||
messageType="error"
|
messageType="error"
|
||||||
showErrorDetails={this.state.showErrorDetails}
|
showErrorDetails={this.state.showErrorDetails}
|
||||||
openNotificationConsole={this.props.openNotificationConsole}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!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}
|
||||||
openNotificationConsole={this.props.openNotificationConsole}
|
|
||||||
link={Constants.Urls.freeTierInformation}
|
link={Constants.Urls.freeTierInformation}
|
||||||
linkText="Learn more"
|
linkText="Learn more"
|
||||||
/>
|
/>
|
||||||
@@ -243,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)}
|
||||||
@@ -472,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)}
|
||||||
@@ -683,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(),
|
||||||
}));
|
}));
|
||||||
@@ -775,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -862,8 +858,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
case "SQL":
|
case "SQL":
|
||||||
case "Mongo":
|
case "Mongo":
|
||||||
return true;
|
return true;
|
||||||
case "Cassandra":
|
|
||||||
return this.props.explorer.hasStorageAnalyticsAfecFeature();
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1064,7 +1058,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
this.setState({ isExecuting: false });
|
this.setState({ isExecuting: false });
|
||||||
this.props.explorer.refreshAllDatabases();
|
this.props.explorer.refreshAllDatabases();
|
||||||
TelemetryProcessor.traceSuccess(Action.CreateCollection, telemetryData, startKey);
|
TelemetryProcessor.traceSuccess(Action.CreateCollection, telemetryData, startKey);
|
||||||
this.props.closePanel();
|
useSidePanel.getState().closeSidePanel();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage: string = getErrorMessage(error);
|
const errorMessage: string = getErrorMessage(error);
|
||||||
this.setState({ isExecuting: false, errorMessage, showErrorDetails: true });
|
this.setState({ isExecuting: false, errorMessage, showErrorDetails: true });
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUti
|
|||||||
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import { SubscriptionType } from "../../../Contracts/SubscriptionType";
|
import { SubscriptionType } from "../../../Contracts/SubscriptionType";
|
||||||
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import * as SharedConstants from "../../../Shared/Constants";
|
import * as SharedConstants from "../../../Shared/Constants";
|
||||||
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";
|
||||||
@@ -15,20 +16,19 @@ 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 {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
closePanel: () => void;
|
|
||||||
openNotificationConsole: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||||
explorer: container,
|
explorer: container,
|
||||||
closePanel,
|
|
||||||
openNotificationConsole,
|
|
||||||
}: AddDatabasePaneProps) => {
|
}: AddDatabasePaneProps) => {
|
||||||
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
let throughput: number;
|
let throughput: number;
|
||||||
let isAutoscaleSelected: boolean;
|
let isAutoscaleSelected: boolean;
|
||||||
let isCostAcknowledged: boolean;
|
let isCostAcknowledged: boolean;
|
||||||
@@ -114,7 +114,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
|
|
||||||
const _onCreateDatabaseSuccess = (offerThroughput: number, startKey: number): void => {
|
const _onCreateDatabaseSuccess = (offerThroughput: number, startKey: number): void => {
|
||||||
setIsExecuting(false);
|
setIsExecuting(false);
|
||||||
closePanel();
|
closeSidePanel();
|
||||||
container.refreshAllDatabases();
|
container.refreshAllDatabases();
|
||||||
const addDatabasePaneSuccessMessage = {
|
const addDatabasePaneSuccessMessage = {
|
||||||
...addDatabasePaneMessage,
|
...addDatabasePaneMessage,
|
||||||
@@ -163,7 +163,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const props: RightPaneFormProps = {
|
const props: RightPaneFormProps = {
|
||||||
expandConsole: openNotificationConsole,
|
|
||||||
formError: formErrors,
|
formError: formErrors,
|
||||||
isExecuting,
|
isExecuting,
|
||||||
submitButtonText: "OK",
|
submitButtonText: "OK",
|
||||||
@@ -174,19 +173,25 @@ 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}
|
||||||
openNotificationConsole={openNotificationConsole}
|
|
||||||
link={Constants.Urls.freeTierInformation}
|
link={Constants.Urls.freeTierInformation}
|
||||||
linkText="Learn more"
|
linkText="Learn more"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<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>
|
||||||
|
|
||||||
@@ -203,36 +208,37 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
value={databaseId}
|
value={databaseId}
|
||||||
onChange={handleonChangeDBId}
|
onChange={handleonChangeDBId}
|
||||||
autoFocus
|
autoFocus
|
||||||
style={{ fontSize: 12 }}
|
styles={getTextFieldStyles()}
|
||||||
styles={{ root: { width: 300 } }}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Stack horizontal>
|
{!isServerlessAccount() && (
|
||||||
<Checkbox
|
<Stack horizontal>
|
||||||
title="Provision shared throughput"
|
<Checkbox
|
||||||
styles={{
|
title="Provision shared throughput"
|
||||||
text: { fontSize: 12 },
|
styles={{
|
||||||
checkbox: { width: 12, height: 12 },
|
text: { fontSize: 12 },
|
||||||
label: { padding: 0, alignItems: "center" },
|
checkbox: { width: 12, height: 12 },
|
||||||
}}
|
label: { padding: 0, alignItems: "center" },
|
||||||
label="Provision throughput"
|
}}
|
||||||
checked={databaseCreateNewShared}
|
label="Provision throughput"
|
||||||
onChange={() => setDatabaseCreateNewShared(!databaseCreateNewShared)}
|
checked={databaseCreateNewShared}
|
||||||
/>
|
onChange={() => setDatabaseCreateNewShared(!databaseCreateNewShared)}
|
||||||
<InfoTooltip>{databaseLevelThroughputTooltipText}</InfoTooltip>
|
/>
|
||||||
</Stack>
|
<InfoTooltip>{databaseLevelThroughputTooltipText}</InfoTooltip>
|
||||||
|
</Stack>
|
||||||
{!isServerlessAccount() && databaseCreateNewShared && (
|
|
||||||
<ThroughputInput
|
|
||||||
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container?.isFirstResourceCreated()}
|
|
||||||
isDatabase={true}
|
|
||||||
isSharded={databaseCreateNewShared}
|
|
||||||
setThroughputValue={(newThroughput: number) => (throughput = newThroughput)}
|
|
||||||
setIsAutoscale={(isAutoscale: boolean) => (isAutoscaleSelected = isAutoscale)}
|
|
||||||
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</Stack>
|
||||||
|
|
||||||
|
{!isServerlessAccount() && databaseCreateNewShared && (
|
||||||
|
<ThroughputInput
|
||||||
|
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
|
||||||
|
isDatabase={true}
|
||||||
|
isSharded={databaseCreateNewShared}
|
||||||
|
setThroughputValue={(newThroughput: number) => (throughput = newThroughput)}
|
||||||
|
setIsAutoscale={(isAutoscale: boolean) => (isAutoscaleSelected = isAutoscale)}
|
||||||
|
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</RightPaneForm>
|
</RightPaneForm>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
||||||
<RightPaneForm
|
<RightPaneForm
|
||||||
expandConsole={[Function]}
|
|
||||||
formError=""
|
formError=""
|
||||||
isExecuting={false}
|
isExecuting={false}
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
@@ -11,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
|
||||||
@@ -39,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,
|
||||||
},
|
},
|
||||||
@@ -83,14 +86,14 @@ 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>
|
||||||
<ThroughputInput
|
</Stack>
|
||||||
isDatabase={true}
|
<ThroughputInput
|
||||||
isSharded={true}
|
isDatabase={true}
|
||||||
onCostAcknowledgeChange={[Function]}
|
isSharded={true}
|
||||||
setIsAutoscale={[Function]}
|
onCostAcknowledgeChange={[Function]}
|
||||||
setThroughputValue={[Function]}
|
setIsAutoscale={[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} />);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Areas } from "../../../Common/Constants";
|
|||||||
import { logError } from "../../../Common/Logger";
|
import { logError } from "../../../Common/Logger";
|
||||||
import { Query } from "../../../Contracts/DataModels";
|
import { Query } from "../../../Contracts/DataModels";
|
||||||
import { Collection } from "../../../Contracts/ViewModels";
|
import { Collection } from "../../../Contracts/ViewModels";
|
||||||
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { trace } from "../../../Shared/Telemetry/TelemetryProcessor";
|
import { trace } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
@@ -11,19 +12,20 @@ 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;
|
||||||
closePanel: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BrowseQueriesPane: FunctionComponent<BrowseQueriesPaneProps> = ({
|
export const BrowseQueriesPane: FunctionComponent<BrowseQueriesPaneProps> = ({
|
||||||
explorer,
|
explorer,
|
||||||
closePanel,
|
|
||||||
}: BrowseQueriesPaneProps): JSX.Element => {
|
}: BrowseQueriesPaneProps): JSX.Element => {
|
||||||
|
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,26 +33,27 @@ 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,
|
||||||
paneTitle: "Open Saved Queries",
|
paneTitle: "Open Saved Queries",
|
||||||
});
|
});
|
||||||
closePanel();
|
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";
|
||||||
const props = {
|
|
||||||
explorer: new Explorer(),
|
|
||||||
closePanel: (): void => undefined,
|
|
||||||
cassandraApiClient: new CassandraAPIDataClient(),
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("CassandraAddCollectionPane Pane", () => {
|
describe("Cassandra add collection pane test", () => {
|
||||||
|
const props = {
|
||||||
|
explorer: new Explorer(),
|
||||||
|
closePanel: (): void => undefined,
|
||||||
|
cassandraApiClient: new CassandraAPIDataClient(),
|
||||||
|
};
|
||||||
|
|
||||||
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,209 +1,97 @@
|
|||||||
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 { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
|
||||||
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 {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
closePanel: () => void;
|
|
||||||
cassandraApiClient: CassandraAPIDataClient;
|
cassandraApiClient: CassandraAPIDataClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectionPaneProps> = ({
|
export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectionPaneProps> = ({
|
||||||
explorer: container,
|
explorer: container,
|
||||||
closePanel,
|
|
||||||
cassandraApiClient,
|
cassandraApiClient,
|
||||||
}: CassandraAddCollectionPaneProps) => {
|
}: CassandraAddCollectionPaneProps) => {
|
||||||
const throughputDefaults = container.collectionCreationDefaults.throughput;
|
let newKeySpaceThroughput: number;
|
||||||
const [createTableQuery, setCreateTableQuery] = useState<string>("CREATE TABLE ");
|
let isNewKeySpaceAutoscale: boolean;
|
||||||
const [keyspaceId, setKeyspaceId] = useState<string>("");
|
let tableThroughput: number;
|
||||||
|
let isTableAutoscale: boolean;
|
||||||
|
let isCostAcknowledged: boolean;
|
||||||
|
|
||||||
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
|
const [newKeyspaceId, setNewKeyspaceId] = useState<string>("");
|
||||||
|
const [existingKeyspaceId, setExistingKeyspaceId] = useState<string>("");
|
||||||
const [tableId, setTableId] = useState<string>("");
|
const [tableId, setTableId] = useState<string>("");
|
||||||
const [throughput, setThroughput] = useState<number>(
|
|
||||||
AddCollectionUtility.getMaxThroughput(container.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,
|
||||||
@@ -241,12 +129,12 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
}
|
}
|
||||||
container.refreshAllDatabases();
|
container.refreshAllDatabases();
|
||||||
setIsExecuting(false);
|
setIsExecuting(false);
|
||||||
closePanel();
|
closeSidePanel();
|
||||||
|
|
||||||
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,130 +144,160 @@ 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 = {
|
||||||
expandConsole: () => container.expandConsole(),
|
formError,
|
||||||
formError: formErrors,
|
|
||||||
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="panelRadioBtnLabel">Use existing</span>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{keyspaceCreateNew && (
|
||||||
|
<Stack className="panelGroupSpacing">
|
||||||
|
<TextField
|
||||||
|
aria-required="true"
|
||||||
|
autoComplete="off"
|
||||||
|
styles={getTextFieldStyles()}
|
||||||
|
pattern="[^/?#\\]*[^/?# \\]"
|
||||||
|
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
||||||
|
placeholder="Type a new keyspace id"
|
||||||
|
size={40}
|
||||||
|
value={newKeyspaceId}
|
||||||
|
onChange={(e, newValue) => setNewKeyspaceId(newValue)}
|
||||||
|
ariaLabel="Keyspace id"
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
|
||||||
|
{!isServerlessAccount() && (
|
||||||
|
<Stack horizontal>
|
||||||
|
<Checkbox
|
||||||
|
label="Provision shared throughput"
|
||||||
|
checked={isKeyspaceShared}
|
||||||
|
styles={{
|
||||||
|
text: { fontSize: 12 },
|
||||||
|
checkbox: { width: 12, height: 12 },
|
||||||
|
label: { padding: 0, alignItems: "center" },
|
||||||
|
}}
|
||||||
|
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) => setIsKeyspaceShared(isChecked)}
|
||||||
|
/>
|
||||||
|
<InfoTooltip>
|
||||||
|
Provisioned throughput at the keyspace level will be shared across unlimited number of tables within
|
||||||
|
the keyspace
|
||||||
|
</InfoTooltip>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!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
|
||||||
|
showFreeTierExceedThroughputTooltip={
|
||||||
|
isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()
|
||||||
|
}
|
||||||
|
isDatabase
|
||||||
|
isSharded
|
||||||
|
setThroughputValue={(throughput: number) => (newKeySpaceThroughput = throughput)}
|
||||||
|
setIsAutoscale={(isAutoscale: boolean) => (isNewKeySpaceAutoscale = isAutoscale)}
|
||||||
|
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack>
|
||||||
|
<Stack horizontal>
|
||||||
|
<span className="mandatoryStar">* </span>
|
||||||
|
<Text className="panelTextBold" variant="small">
|
||||||
|
Enter CQL command to create the table.{" "}
|
||||||
|
<Link href="https://aka.ms/cassandra-create-table" target="_blank">
|
||||||
|
Learn More
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack horizontal verticalAlign="center">
|
||||||
|
<Text variant="small" style={{ marginRight: 4 }}>
|
||||||
|
{`CREATE TABLE ${keyspaceCreateNew ? newKeyspaceId : existingKeyspaceId}.`}
|
||||||
|
</Text>
|
||||||
|
<TextField
|
||||||
|
underlined
|
||||||
|
styles={getTextFieldStyles({ fontSize: 12, width: 150 })}
|
||||||
|
aria-required="true"
|
||||||
|
ariaLabel="addCollection-tableId"
|
||||||
|
autoComplete="off"
|
||||||
|
pattern="[^/?#\\]*[^/?# \\]"
|
||||||
|
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
||||||
|
placeholder="Enter table Id"
|
||||||
|
size={20}
|
||||||
|
value={tableId}
|
||||||
|
onChange={(e, newValue) => setTableId(newValue)}
|
||||||
/>
|
/>
|
||||||
<span className="throughputInputRadioBtnLabel">Use existing</span>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
aria-required="true"
|
styles={getTextFieldStyles()}
|
||||||
autoComplete="off"
|
|
||||||
pattern="[^/?#\\]*[^/?# \\]"
|
|
||||||
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
|
||||||
list={keyspaceCreateNew ? "" : "keyspacesList"}
|
|
||||||
placeholder={keyspaceCreateNew ? "Type a new keyspace id" : "Choose existing keyspace id"}
|
|
||||||
size={40}
|
|
||||||
data-test="addCollection-keyspaceId"
|
|
||||||
value={keyspaceId}
|
|
||||||
onChange={(e, newValue) => setKeyspaceId(newValue)}
|
|
||||||
ariaLabel="Keyspace id"
|
|
||||||
autoFocus
|
|
||||||
/>
|
|
||||||
<datalist id="keyspacesList">
|
|
||||||
{keyspaceIds?.map((id: string, index: number) => (
|
|
||||||
<option key={index}>{id}</option>
|
|
||||||
))}
|
|
||||||
</datalist>
|
|
||||||
{canConfigureThroughput && keyspaceCreateNew && (
|
|
||||||
<div className="databaseProvision">
|
|
||||||
<input
|
|
||||||
tabIndex={0}
|
|
||||||
type="checkbox"
|
|
||||||
id="keyspaceSharedThroughput"
|
|
||||||
title="Provision shared throughput"
|
|
||||||
checked={keyspaceHasSharedOffer}
|
|
||||||
onChange={(e) => setKeyspaceHasSharedOffer(e.target.checked)}
|
|
||||||
/>
|
|
||||||
<span className="databaseProvisionText" aria-label="Provision keyspace throughput">
|
|
||||||
Provision keyspace throughput
|
|
||||||
</span>
|
|
||||||
<InfoTooltip>
|
|
||||||
Provisioned throughput at the keyspace level will be shared across unlimited number of tables within the
|
|
||||||
keyspace
|
|
||||||
</InfoTooltip>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{canConfigureThroughput && keyspaceCreateNew && keyspaceHasSharedOffer && (
|
|
||||||
<div>
|
|
||||||
<ThroughputInput
|
|
||||||
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container.isFirstResourceCreated()}
|
|
||||||
isDatabase
|
|
||||||
isSharded
|
|
||||||
setThroughputValue={(throughput: number) => setKeyspaceThroughput(throughput)}
|
|
||||||
setIsAutoscale={(isAutoscale: boolean) => setIsSharedAutoPilotSelected(isAutoscale)}
|
|
||||||
onCostAcknowledgeChange={(isAcknowledge: boolean) => {
|
|
||||||
setSharedThroughputSpendAck(isAcknowledge);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="seconddivpadding">
|
|
||||||
<p>
|
|
||||||
<Label required>
|
|
||||||
Enter CQL command to create the table.
|
|
||||||
<a href="https://aka.ms/cassandra-create-table" target="_blank" rel="noreferrer">
|
|
||||||
Learn More
|
|
||||||
</a>
|
|
||||||
</Label>
|
|
||||||
</p>
|
|
||||||
<div aria-label={createTableQuery} style={{ float: "left", paddingTop: "3px", paddingRight: "3px" }}>
|
|
||||||
{createTableQuery}
|
|
||||||
</div>
|
|
||||||
<TextField
|
|
||||||
aria-required="true"
|
|
||||||
ariaLabel="addCollection-tableId"
|
|
||||||
autoComplete="off"
|
|
||||||
pattern="[^/?#\\]*[^/?# \\]"
|
|
||||||
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
|
||||||
placeholder="Enter tableId"
|
|
||||||
size={20}
|
|
||||||
className="textfontclr"
|
|
||||||
value={tableId}
|
|
||||||
onChange={(e, newValue) => setTableId(newValue)}
|
|
||||||
style={{ marginBottom: "5px" }}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
multiline
|
multiline
|
||||||
id="editor-area"
|
id="editor-area"
|
||||||
rows={5}
|
rows={5}
|
||||||
@@ -387,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"
|
||||||
@@ -405,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 && !useDatabases.getState().isFirstResourceCreated()}
|
||||||
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container.isFirstResourceCreated()}
|
isDatabase={false}
|
||||||
isDatabase={false}
|
isSharded={false}
|
||||||
isSharded={false}
|
setThroughputValue={(throughput: number) => (tableThroughput = throughput)}
|
||||||
setThroughputValue={(throughput: number) => setThroughput(throughput)}
|
setIsAutoscale={(isAutoscale: boolean) => (isTableAutoscale = isAutoscale)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => setIsAutoPilotSelected(isAutoscale)}
|
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
||||||
onCostAcknowledgeChange={(isAcknowledge: boolean) => {
|
/>
|
||||||
setThroughputSpendAck(isAcknowledge);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</RightPaneForm>
|
</RightPaneForm>
|
||||||
|
|||||||
@@ -1,164 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`CassandraAddCollectionPane Pane should render Default properly 1`] = `
|
|
||||||
<RightPaneForm
|
|
||||||
expandConsole={[Function]}
|
|
||||||
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>
|
|
||||||
`;
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
|
||||||
import * as Constants from "../../Common/Constants";
|
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
import { KeyCodes } from "../../Common/Constants";
|
|
||||||
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
|
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import Explorer from "../Explorer";
|
|
||||||
|
|
||||||
// TODO: Use specific actions for logging telemetry data
|
|
||||||
export abstract class ContextualPaneBase extends WaitsForTemplateViewModel {
|
|
||||||
private initalFocusedElement: HTMLElement | undefined;
|
|
||||||
public id: string;
|
|
||||||
public container: Explorer;
|
|
||||||
public firstFieldHasFocus: ko.Observable<boolean>;
|
|
||||||
public formErrorsDetails: ko.Observable<string>;
|
|
||||||
public formErrors: ko.Observable<string>;
|
|
||||||
public title: ko.Observable<string>;
|
|
||||||
public visible: ko.Observable<boolean>;
|
|
||||||
public isExecuting: ko.Observable<boolean>;
|
|
||||||
|
|
||||||
constructor(options: ViewModels.PaneOptions) {
|
|
||||||
super();
|
|
||||||
this.id = options.id;
|
|
||||||
this.container = options.container;
|
|
||||||
this.visible = options.visible || ko.observable(false);
|
|
||||||
this.firstFieldHasFocus = ko.observable<boolean>(false);
|
|
||||||
this.formErrors = ko.observable<string>();
|
|
||||||
this.title = ko.observable<string>();
|
|
||||||
this.formErrorsDetails = ko.observable<string>();
|
|
||||||
this.isExecuting = ko.observable<boolean>(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public cancel() {
|
|
||||||
this.close();
|
|
||||||
this.container.isAccountReady() &&
|
|
||||||
TelemetryProcessor.trace(Action.ContextualPane, ActionModifiers.Close, {
|
|
||||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
|
||||||
paneTitle: this.title(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public close() {
|
|
||||||
this.visible(false);
|
|
||||||
this.isExecuting(false);
|
|
||||||
this.resetData();
|
|
||||||
this.resetFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
public open() {
|
|
||||||
this.initalFocusedElement = document.activeElement as HTMLElement;
|
|
||||||
this.visible(true);
|
|
||||||
this.firstFieldHasFocus(true);
|
|
||||||
this.resizePane();
|
|
||||||
this.container.isAccountReady() &&
|
|
||||||
TelemetryProcessor.trace(Action.ContextualPane, ActionModifiers.Open, {
|
|
||||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
|
||||||
paneTitle: this.title(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public resetData() {
|
|
||||||
this.firstFieldHasFocus(false);
|
|
||||||
this.formErrors(null);
|
|
||||||
this.formErrorsDetails(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public showErrorDetails() {
|
|
||||||
this.container.expandConsole();
|
|
||||||
}
|
|
||||||
|
|
||||||
public submit() {
|
|
||||||
this.close();
|
|
||||||
event.stopPropagation();
|
|
||||||
this.container.isAccountReady() &&
|
|
||||||
TelemetryProcessor.trace(Action.ContextualPane, ActionModifiers.Submit, {
|
|
||||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
|
||||||
paneTitle: this.title(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public onCloseKeyPress(source: any, event: KeyboardEvent): boolean {
|
|
||||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
|
||||||
this.close();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public onPaneKeyDown(source: any, event: KeyboardEvent): boolean {
|
|
||||||
if (event.keyCode === KeyCodes.Escape) {
|
|
||||||
this.close();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public onSubmitKeyPress(source: any, event: KeyboardEvent): boolean {
|
|
||||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
|
||||||
this.submit();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private resizePane(): void {
|
|
||||||
const paneElement: HTMLElement = document.getElementById(this.id);
|
|
||||||
const notificationConsoleElement: HTMLElement = document.getElementById("explorerNotificationConsole");
|
|
||||||
const newPaneElementHeight = window.innerHeight - $(notificationConsoleElement).height();
|
|
||||||
|
|
||||||
$(paneElement).height(newPaneElementHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
private resetFocus(): void {
|
|
||||||
if (this.initalFocusedElement) {
|
|
||||||
this.initalFocusedElement.focus();
|
|
||||||
this.initalFocusedElement = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@ import React, { FormEvent, FunctionComponent, useEffect, useState } from "react"
|
|||||||
import { HttpStatusCodes } from "../../../Common/Constants";
|
import { HttpStatusCodes } from "../../../Common/Constants";
|
||||||
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
||||||
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient";
|
import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient";
|
||||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
@@ -26,7 +27,6 @@ export interface CopyNotebookPanelProps {
|
|||||||
container: Explorer;
|
container: Explorer;
|
||||||
junoClient: JunoClient;
|
junoClient: JunoClient;
|
||||||
gitHubOAuthService: GitHubOAuthService;
|
gitHubOAuthService: GitHubOAuthService;
|
||||||
closePanel: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
||||||
@@ -35,8 +35,8 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
|||||||
container,
|
container,
|
||||||
junoClient,
|
junoClient,
|
||||||
gitHubOAuthService,
|
gitHubOAuthService,
|
||||||
closePanel,
|
|
||||||
}: CopyNotebookPanelProps) => {
|
}: CopyNotebookPanelProps) => {
|
||||||
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
const [isExecuting, setIsExecuting] = useState<boolean>();
|
const [isExecuting, setIsExecuting] = useState<boolean>();
|
||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
const [pinnedRepos, setPinnedRepos] = useState<IPinnedRepo[]>();
|
const [pinnedRepos, setPinnedRepos] = useState<IPinnedRepo[]>();
|
||||||
@@ -84,7 +84,7 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
NotificationConsoleUtils.logConsoleInfo(`Successfully copied ${name} to ${destination}`);
|
NotificationConsoleUtils.logConsoleInfo(`Successfully copied ${name} to ${destination}`);
|
||||||
closePanel();
|
closeSidePanel();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = getErrorMessage(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
setFormError(`Failed to copy ${name} to ${destination}`);
|
setFormError(`Failed to copy ${name} to ${destination}`);
|
||||||
@@ -130,7 +130,6 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
|||||||
isExecuting: isExecuting,
|
isExecuting: isExecuting,
|
||||||
submitButtonText: "OK",
|
submitButtonText: "OK",
|
||||||
onSubmit: () => submit(),
|
onSubmit: () => submit(),
|
||||||
expandConsole: () => container.expandConsole(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const copyNotebookPaneProps: CopyNotebookPaneProps = {
|
const copyNotebookPaneProps: CopyNotebookPaneProps = {
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { deleteCollection } from "../../../Common/dataAccess/deleteCollection";
|
|||||||
import DeleteFeedback from "../../../Common/DeleteFeedback";
|
import DeleteFeedback from "../../../Common/DeleteFeedback";
|
||||||
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||||
import { Collection } from "../../../Contracts/ViewModels";
|
import { Collection } from "../../../Contracts/ViewModels";
|
||||||
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import { DefaultExperienceUtility } from "../../../Shared/DefaultExperienceUtility";
|
import { DefaultExperienceUtility } from "../../../Shared/DefaultExperienceUtility";
|
||||||
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";
|
||||||
@@ -12,28 +13,31 @@ 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;
|
||||||
closePanel: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectionConfirmationPaneProps> = ({
|
export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectionConfirmationPaneProps> = ({
|
||||||
explorer,
|
explorer,
|
||||||
closePanel,
|
|
||||||
}: DeleteCollectionConfirmationPaneProps) => {
|
}: DeleteCollectionConfirmationPaneProps) => {
|
||||||
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
const [deleteCollectionFeedback, setDeleteCollectionFeedback] = useState<string>("");
|
const [deleteCollectionFeedback, setDeleteCollectionFeedback] = useState<string>("");
|
||||||
const [inputCollectionName, setInputCollectionName] = useState<string>("");
|
const [inputCollectionName, setInputCollectionName] = useState<string>("");
|
||||||
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
|
||||||
);
|
);
|
||||||
@@ -79,7 +83,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
closePanel();
|
closeSidePanel();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = getErrorMessage(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
|
|
||||||
@@ -102,7 +106,6 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
isExecuting,
|
isExecuting,
|
||||||
submitButtonText: "OK",
|
submitButtonText: "OK",
|
||||||
onSubmit,
|
onSubmit,
|
||||||
expandConsole: () => explorer.expandConsole(),
|
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<RightPaneForm {...props}>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user