mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-23 02:41:39 +00:00
Compare commits
77 Commits
all-checks
...
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 | ||
|
|
c42a10faa5 | ||
|
|
0d79f01304 | ||
|
|
eae5b2219e | ||
|
|
2fda881770 | ||
|
|
35f8fa8324 | ||
|
|
0e413430dc | ||
|
|
afd7f43eb8 |
@@ -44,7 +44,6 @@ src/Definitions/png.d.ts
|
||||
src/Definitions/svg.d.ts
|
||||
src/Explorer/ComponentRegisterer.test.ts
|
||||
src/Explorer/ComponentRegisterer.ts
|
||||
src/Explorer/ContextMenuButtonFactory.ts
|
||||
src/Explorer/Controls/CollapsiblePanel/CollapsiblePanelComponent.ts
|
||||
src/Explorer/Controls/DiffEditor/DiffEditorComponent.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/NotebookContentItem.ts
|
||||
src/Explorer/Notebook/NotebookUtil.ts
|
||||
src/Explorer/OpenActions.test.ts
|
||||
src/Explorer/OpenActions.ts
|
||||
src/Explorer/OpenActionsStubs.ts
|
||||
src/Explorer/Panes/AddDatabasePane.ts
|
||||
src/Explorer/Panes/AddDatabasePane.test.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/SetupNotebooksPane.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/QueryBuilderViewModel.ts
|
||||
src/Explorer/Tables/QueryBuilder/QueryClauseViewModel.ts
|
||||
src/Explorer/Tables/QueryBuilder/QueryViewModel.ts
|
||||
src/Explorer/Tables/TableDataClient.ts
|
||||
src/Explorer/Tables/TableEntityProcessor.ts
|
||||
src/Explorer/Tables/Utilities.ts
|
||||
@@ -148,14 +141,11 @@ src/Explorer/Tabs/DocumentsTab.test.ts
|
||||
src/Explorer/Tabs/DocumentsTab.ts
|
||||
src/Explorer/Tabs/GraphTab.ts
|
||||
src/Explorer/Tabs/MongoDocumentsTab.ts
|
||||
src/Explorer/Tabs/MongoQueryTab.ts
|
||||
src/Explorer/Tabs/MongoShellTab.ts
|
||||
# src/Explorer/Tabs/MongoQueryTab.ts
|
||||
# src/Explorer/Tabs/MongoShellTab.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/StoredProcedureTab.ts
|
||||
# src/Explorer/Tabs/StoredProcedureTab.ts
|
||||
src/Explorer/Tabs/TabComponents.ts
|
||||
src/Explorer/Tabs/TabsBase.ts
|
||||
src/Explorer/Tabs/TriggerTab.ts
|
||||
@@ -208,9 +198,6 @@ src/ResourceProvider/IResourceProviderClient.test.ts
|
||||
src/ResourceProvider/IResourceProviderClient.ts
|
||||
src/ResourceProvider/ResourceProviderClient.ts
|
||||
src/ResourceProvider/ResourceProviderClientFactory.ts
|
||||
src/RouteHandlers/RouteHandler.ts
|
||||
src/RouteHandlers/TabRouteHandler.test.ts
|
||||
src/RouteHandlers/TabRouteHandler.ts
|
||||
src/Shared/Constants.ts
|
||||
src/Shared/DefaultExperienceUtility.test.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/GraphExplorer.test.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/GraphExplorerAdapter.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/LeftPaneComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx
|
||||
|
||||
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
@@ -92,11 +92,11 @@ jobs:
|
||||
name: dist
|
||||
path: dist/
|
||||
- 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:
|
||||
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
||||
- 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:
|
||||
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
||||
endtoendemulator:
|
||||
@@ -214,10 +214,3 @@ jobs:
|
||||
name: packages
|
||||
with:
|
||||
path: "*.nupkg"
|
||||
allcheckspass:
|
||||
name: "All Checks Pass"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: "WyriHaximus/github-action-wait-for-status@v1.3.1"
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
2
.github/workflows/cleanup.yml
vendored
2
.github/workflows/cleanup.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# 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
|
||||
jobs:
|
||||
|
||||
@@ -724,45 +724,24 @@ execute-sproc-params-pane {
|
||||
|
||||
.results-container,
|
||||
.errors-container {
|
||||
padding: @MediumSpace 0px 0px @MediumSpace;
|
||||
height: 100%;
|
||||
.flex-display();
|
||||
.flex-direction();
|
||||
overflow: hidden;
|
||||
|
||||
.toggles {
|
||||
height: @ToggleHeight;
|
||||
width: @ToggleWidth;
|
||||
margin-left: @MediumSpace;
|
||||
|
||||
&:focus {
|
||||
.focus();
|
||||
}
|
||||
|
||||
.tab {
|
||||
margin-right: @MediumSpace;
|
||||
}
|
||||
|
||||
.toggleSwitch {
|
||||
.toggleSwitch();
|
||||
}
|
||||
|
||||
.selectedToggle {
|
||||
.selectedToggle();
|
||||
}
|
||||
|
||||
.unselectedToggle {
|
||||
.unselectedToggle();
|
||||
}
|
||||
}
|
||||
|
||||
.enterInputParameters {
|
||||
padding: @LargeSpace @MediumSpace;
|
||||
}
|
||||
|
||||
div[role="tabpanel"] {
|
||||
height: 100%;
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.errors-container {
|
||||
padding-left: (2 * @MediumSpace);
|
||||
padding: @MediumSpace 0px 0px @MediumSpace;
|
||||
.errors-header {
|
||||
font-weight: 700;
|
||||
font-size: @DefaultFontSize;
|
||||
@@ -3085,3 +3064,14 @@ settings-pane {
|
||||
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 {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.trigger-field {
|
||||
width: 40%;
|
||||
margin-top: 10px
|
||||
}
|
||||
.trigger-form {
|
||||
padding: 10px 30px 10px 30px;
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
.resourceTree {
|
||||
height: 100%;
|
||||
width: 20%;
|
||||
flex: 0 0 auto;
|
||||
.main {
|
||||
height: 100%;
|
||||
|
||||
132
package-lock.json
generated
132
package-lock.json
generated
@@ -3709,14 +3709,84 @@
|
||||
}
|
||||
},
|
||||
"@nteract/editor": {
|
||||
"version": "10.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@nteract/editor/-/editor-10.1.2.tgz",
|
||||
"integrity": "sha512-Wtj0kJUSoBZsWUh82JGt6miqYS0jt0k+3SD3cnW9socayxp2KB0Qbqhh2NtrF9ysxVHWnQT8iUarJjpGIdNyng==",
|
||||
"version": "10.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@nteract/editor/-/editor-10.1.12.tgz",
|
||||
"integrity": "sha512-bsUrCctukjWdpKNWQOQmhfxMCQ/SBVIO6+RkazI4y4dVeeP3KMP8nxfhzIbzTMNSkyynps/deZFjpDWqRhG+Dg==",
|
||||
"requires": {
|
||||
"@nteract/messaging": "^7.0.10",
|
||||
"@nteract/outputs": "^3.0.9",
|
||||
"codemirror": "5.57.0",
|
||||
"@nteract/messaging": "^7.0.19",
|
||||
"@nteract/outputs": "^3.0.11",
|
||||
"codemirror": "5.61.1",
|
||||
"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": {
|
||||
@@ -5650,6 +5720,15 @@
|
||||
"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": {
|
||||
"version": "6.8.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-6.8.7.tgz",
|
||||
@@ -8058,9 +8137,9 @@
|
||||
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
|
||||
},
|
||||
"codemirror": {
|
||||
"version": "5.57.0",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.57.0.tgz",
|
||||
"integrity": "sha512-WGc6UL7Hqt+8a6ZAsj/f1ApQl3NPvHY/UQSzG6fB6l4BjExgVdhFaxd7mRTw1UCiYe/6q86zHP+kfvBQcZGvUg=="
|
||||
"version": "5.61.1",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.61.1.tgz",
|
||||
"integrity": "sha512-+D1NZjAucuzE93vJGbAaXzvoBHwp9nJZWWWF9utjv25+5AZUiah6CIlfb4ikG4MoDsFsCG8niiJH5++OO2LgIQ=="
|
||||
},
|
||||
"collapse-white-space": {
|
||||
"version": "1.0.6",
|
||||
@@ -17690,12 +17769,6 @@
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"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": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
@@ -18499,9 +18572,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"lodash-es": {
|
||||
"version": "4.17.20",
|
||||
@@ -18728,9 +18801,9 @@
|
||||
"integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg=="
|
||||
},
|
||||
"marked": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-2.0.3.tgz",
|
||||
"integrity": "sha512-5otztIIcJfPc2qGTN8cVtOJEjNJZ0jwa46INMagrYfk0EvqtRuEHLsEe0LrFS0/q+ZRKT0+kXK7P2T1AN5lWRA==",
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-2.0.6.tgz",
|
||||
"integrity": "sha512-S2mYj0FzTQa0dLddssqwRVW4EOJOVJ355Xm2Vcbm+LU7GQRGWvwbO5K87OaPSOux2AwTSgtPPaXmc8sDPrhn2A==",
|
||||
"dev": true
|
||||
},
|
||||
"martinez-polygon-clipping": {
|
||||
@@ -21635,6 +21708,11 @@
|
||||
"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": {
|
||||
"version": "12.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-12.2.1.tgz",
|
||||
@@ -24367,12 +24445,6 @@
|
||||
"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": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
@@ -24388,9 +24460,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
|
||||
"integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==",
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz",
|
||||
"integrity": "sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew==",
|
||||
"dev": true
|
||||
},
|
||||
"typestyle": {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"@nteract/data-explorer": "8.0.3",
|
||||
"@nteract/directory-listing": "2.0.6",
|
||||
"@nteract/dropdown-menu": "1.0.1",
|
||||
"@nteract/editor": "10.1.2",
|
||||
"@nteract/editor": "10.1.12",
|
||||
"@nteract/fixtures": "2.3.0",
|
||||
"@nteract/iron-icons": "1.0.0",
|
||||
"@nteract/jupyter-widgets": "2.0.0",
|
||||
@@ -89,6 +89,7 @@
|
||||
"react-i18next": "11.8.5",
|
||||
"react-notification-system": "0.2.17",
|
||||
"react-redux": "7.1.3",
|
||||
"react-splitter-layout": "4.0.0",
|
||||
"redux": "4.0.4",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rx-jupyter": "5.5.12",
|
||||
@@ -123,6 +124,7 @@
|
||||
"@types/react-dom": "17.0.3",
|
||||
"@types/react-notification-system": "0.2.39",
|
||||
"@types/react-redux": "7.1.7",
|
||||
"@types/react-splitter-layout": "3.0.1",
|
||||
"@types/sanitize-html": "1.27.2",
|
||||
"@types/sinon": "2.3.3",
|
||||
"@types/styled-components": "5.1.1",
|
||||
@@ -172,7 +174,7 @@
|
||||
"tslint": "5.11.0",
|
||||
"tslint-microsoft-contrib": "6.0.0",
|
||||
"typedoc": "0.20.36",
|
||||
"typescript": "4.2.4",
|
||||
"typescript": "4.3.4",
|
||||
"url-loader": "1.1.1",
|
||||
"wait-on": "4.0.2",
|
||||
"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));
|
||||
});
|
||||
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, () => {
|
||||
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;
|
||||
}
|
||||
|
||||
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 {
|
||||
public static ResourceTree: string = "Resource Tree";
|
||||
public static ContextualPane: string = "Contextual Pane";
|
||||
|
||||
@@ -83,7 +83,7 @@ export function client(): Cosmos.CosmosClient {
|
||||
if (_client) return _client;
|
||||
const options: Cosmos.CosmosClientOptions = {
|
||||
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
|
||||
...(!userContext.features.enableAadDataPlane && { key: userContext.masterKey }),
|
||||
key: userContext.masterKey,
|
||||
tokenProvider,
|
||||
connectionPolicy: {
|
||||
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();
|
||||
}
|
||||
@@ -32,7 +32,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
|
||||
<DatePicker
|
||||
className="addEntityDatePicker"
|
||||
placeholder={entityValuePlaceholder}
|
||||
value={entityValue && new Date(entityValue)}
|
||||
value={entityValue ? new Date(entityValue) : new Date()}
|
||||
ariaLabel={entityValuePlaceholder}
|
||||
onSelectDate={onSelectDate}
|
||||
disabled={isEntityValueDisable}
|
||||
@@ -59,7 +59,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
|
||||
disabled={isEntityValueDisable}
|
||||
type={entityValueType}
|
||||
placeholder={entityValuePlaceholder}
|
||||
value={typeof entityValue === "string" && entityValue}
|
||||
value={typeof entityValue === "string" ? entityValue : ""}
|
||||
onChange={onEntityValueChange}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Collection } from "../Contracts/ViewModels";
|
||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||
import { updateUserContext } from "../UserContext";
|
||||
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
|
||||
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
|
||||
|
||||
const databaseId = "testDB";
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ export function queryDocuments(
|
||||
headers: response.headers,
|
||||
};
|
||||
}
|
||||
errorHandling(response, "querying documents", params);
|
||||
await errorHandling(response, "querying documents", params);
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
@@ -153,11 +153,11 @@ export function readDocument(
|
||||
),
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
.then(async (response) => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
return errorHandling(response, "reading document", params);
|
||||
return await errorHandling(response, "reading document", params);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -192,11 +192,11 @@ export function createDocument(
|
||||
...authHeaders(),
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
.then(async (response) => {
|
||||
if (response.ok) {
|
||||
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()),
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
.then(async (response) => {
|
||||
if (response.ok) {
|
||||
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()),
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
.then(async (response) => {
|
||||
if (response.ok) {
|
||||
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) {
|
||||
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 DataModels from "../Contracts/DataModels";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import Explorer from "../Explorer/Explorer";
|
||||
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
|
||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||
import { useDatabases } from "../Explorer/useDatabases";
|
||||
import { userContext } from "../UserContext";
|
||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||
import * as QueryUtils from "../Utils/QueryUtils";
|
||||
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
||||
import { createCollection } from "./dataAccess/createCollection";
|
||||
import { createDocument } from "./dataAccess/createDocument";
|
||||
import { deleteDocument } from "./dataAccess/deleteDocument";
|
||||
import { queryDocuments } from "./dataAccess/queryDocuments";
|
||||
import { queryDocumentsPage } from "./dataAccess/queryDocumentsPage";
|
||||
import { handleError } from "./ErrorHandlingUtils";
|
||||
|
||||
export class QueriesClient {
|
||||
@@ -100,45 +98,35 @@ export class QueriesClient {
|
||||
|
||||
const options: any = { enableCrossPartitionQuery: true };
|
||||
const clearMessage = NotificationConsoleUtils.logConsoleProgress("Fetching saved queries");
|
||||
const queryIterator: QueryIterator<ItemDefinition & Resource> = queryDocuments(
|
||||
const results = await queryDocuments(
|
||||
SavedQueries.DatabaseName,
|
||||
SavedQueries.CollectionName,
|
||||
this.fetchQueriesQuery(),
|
||||
options
|
||||
);
|
||||
const fetchQueries = async (firstItemIndex: number): Promise<ViewModels.QueryResults> =>
|
||||
await queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex);
|
||||
return QueryUtils.queryAllPages(fetchQueries)
|
||||
.then(
|
||||
(results: ViewModels.QueryResults) => {
|
||||
let queries: DataModels.Query[] = _.map(results.documents, (document: DataModels.Query) => {
|
||||
if (!document) {
|
||||
return undefined;
|
||||
}
|
||||
const { id, resourceId, query, queryName } = document;
|
||||
const parsedQuery: DataModels.Query = {
|
||||
resourceId: resourceId,
|
||||
queryName: queryName,
|
||||
query: query,
|
||||
id: id,
|
||||
};
|
||||
try {
|
||||
this.validateQuery(parsedQuery);
|
||||
return parsedQuery;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
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());
|
||||
).fetchAll();
|
||||
|
||||
let queries: DataModels.Query[] = _.map(results.resources, (document: DataModels.Query) => {
|
||||
if (!document) {
|
||||
return undefined;
|
||||
}
|
||||
const { id, resourceId, query, queryName } = document;
|
||||
const parsedQuery: DataModels.Query = {
|
||||
resourceId: resourceId,
|
||||
queryName: queryName,
|
||||
query: query,
|
||||
id: id,
|
||||
};
|
||||
try {
|
||||
this.validateQuery(parsedQuery);
|
||||
return parsedQuery;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery);
|
||||
NotificationConsoleUtils.logConsoleInfo("Successfully fetched saved queries");
|
||||
clearMessage();
|
||||
return queries;
|
||||
}
|
||||
|
||||
public async deleteQuery(query: DataModels.Query): Promise<void> {
|
||||
@@ -189,7 +177,7 @@ export class QueriesClient {
|
||||
|
||||
private findQueriesCollection(): ViewModels.Collection {
|
||||
const queriesDatabase: ViewModels.Database = _.find(
|
||||
this.container.databases(),
|
||||
useDatabases.getState().databases,
|
||||
(database: ViewModels.Database) => database.id() === SavedQueries.DatabaseName
|
||||
);
|
||||
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 {
|
||||
Horizontal = "horizontal",
|
||||
Vertical = "vertical",
|
||||
@@ -28,14 +24,12 @@ export class Splitter {
|
||||
public lastX!: number;
|
||||
public lastWidth!: number;
|
||||
|
||||
private isCollapsed: ko.Observable<boolean>;
|
||||
private bounds: SplitterBounds;
|
||||
private direction: SplitterDirection;
|
||||
|
||||
constructor(options: SplitterOptions) {
|
||||
this.splitterId = options.splitterId;
|
||||
this.leftSideId = options.leftId;
|
||||
this.isCollapsed = ko.observable<boolean>(false);
|
||||
this.bounds = options.bounds;
|
||||
this.direction = options.direction;
|
||||
this.initialize();
|
||||
@@ -83,23 +77,4 @@ export class Splitter {
|
||||
};
|
||||
|
||||
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;
|
||||
allowedJunoOrigins: string[];
|
||||
enableSchemaAnalyzer: boolean;
|
||||
msalRedirectURI?: string;
|
||||
}
|
||||
|
||||
// Default configuration
|
||||
@@ -119,6 +120,14 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
|
||||
const armAPIVersion = params.get("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")) {
|
||||
const platform = params.get("platform");
|
||||
switch (platform) {
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface DatabaseAccountExtendedProperties {
|
||||
enableAnalyticalStorage?: boolean;
|
||||
isVirtualNetworkFilterEnabled?: boolean;
|
||||
ipRules?: IpRule[];
|
||||
privateEndpointConnections?: unknown[];
|
||||
}
|
||||
|
||||
export interface DatabaseAccountResponseLocation {
|
||||
@@ -391,16 +392,6 @@ export interface GeospatialConfig {
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface GatewayDatabaseAccount {
|
||||
MediaLink: string;
|
||||
DatabasesLink: string;
|
||||
MaxMediaStorageUsageInMB: number;
|
||||
CurrentMediaStorageUsageInMB: number;
|
||||
EnableMultipleWriteLocations?: boolean;
|
||||
WritableLocations: RegionEndpoint[];
|
||||
ReadableLocations: RegionEndpoint[];
|
||||
}
|
||||
|
||||
export interface RegionEndpoint {
|
||||
name: string;
|
||||
documentAccountEndpoint: string;
|
||||
@@ -421,13 +412,6 @@ export interface AccountKeys {
|
||||
secondaryReadonlyMasterKey: string;
|
||||
}
|
||||
|
||||
export interface AfecFeature {
|
||||
id: string;
|
||||
name: string;
|
||||
properties: { state: string };
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface OperationStatus {
|
||||
status: string;
|
||||
id?: string;
|
||||
@@ -507,91 +491,6 @@ export interface MongoParameters extends RpParameters {
|
||||
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 {
|
||||
freeKB: number;
|
||||
totalKB: number;
|
||||
|
||||
@@ -3,11 +3,10 @@ import {
|
||||
Resource,
|
||||
StoredProcedureDefinition,
|
||||
TriggerDefinition,
|
||||
UserDefinedFunctionDefinition,
|
||||
UserDefinedFunctionDefinition
|
||||
} from "@azure/cosmos";
|
||||
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||
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 ConflictId from "../Explorer/Tree/ConflictId";
|
||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||
@@ -15,6 +14,7 @@ import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
||||
import Trigger from "../Explorer/Tree/Trigger";
|
||||
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
|
||||
import { SelfServeType } from "../SelfServe/SelfServeUtils";
|
||||
import { CollectionCreationDefaults } from "../UserContext";
|
||||
import { SqlTriggerResource } from "../Utils/arm/generatedClients/cosmos/types";
|
||||
import * as DataModels from "./DataModels";
|
||||
import { SubscriptionType } from "./SubscriptionType";
|
||||
@@ -89,7 +89,6 @@ export interface Database extends TreeNode {
|
||||
|
||||
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
|
||||
|
||||
selectDatabase(): void;
|
||||
expandDatabase(): Promise<void>;
|
||||
collapseDatabase(): void;
|
||||
|
||||
@@ -275,8 +274,6 @@ export interface TabOptions {
|
||||
tabKind: CollectionTabKind;
|
||||
title: string;
|
||||
tabPath: string;
|
||||
hashLocation: string;
|
||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]) => void;
|
||||
isTabsContentExpanded?: ko.Observable<boolean>;
|
||||
onLoadStartKey?: number;
|
||||
|
||||
@@ -287,6 +284,7 @@ export interface TabOptions {
|
||||
rid?: string;
|
||||
node?: TreeNode;
|
||||
theme?: string;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
export interface DocumentsTabOptions extends TabOptions {
|
||||
@@ -372,6 +370,7 @@ export enum TerminalKind {
|
||||
Default = 0,
|
||||
Mongo = 1,
|
||||
Cassandra = 2,
|
||||
PostgreSQL = 3
|
||||
}
|
||||
|
||||
export interface DataExplorerInputsFrame {
|
||||
@@ -411,25 +410,6 @@ export interface SelfServeFrameInputs {
|
||||
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 {
|
||||
public readonly language: string;
|
||||
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 { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent";
|
||||
import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent";
|
||||
import { EditorComponent } from "./Controls/Editor/EditorComponent";
|
||||
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
|
||||
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
|
||||
|
||||
ko.components.register("editor", new EditorComponent());
|
||||
ko.components.register("json-editor", new JsonEditorComponent());
|
||||
ko.components.register("diff-editor", new DiffEditorComponent());
|
||||
ko.components.register("dynamic-list", DynamicListComponent);
|
||||
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
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
|
||||
@@ -114,15 +111,6 @@ export interface CommandButtonComponentProps {
|
||||
* Aria-label for the button
|
||||
*/
|
||||
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> {
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
import React from "react";
|
||||
import { shallow, mount } from "enzyme";
|
||||
import { DefaultDirectoryDropdownComponent, DefaultDirectoryDropdownProps } from "./DefaultDirectoryDropdownComponent";
|
||||
import { Tenant } from "../../../Contracts/DataModels";
|
||||
|
||||
const createBlankProps = (): DefaultDirectoryDropdownProps => {
|
||||
return {
|
||||
defaultDirectoryId: "",
|
||||
directories: [],
|
||||
onDefaultDirectoryChange: jest.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
const createBlankDirectory = (): Tenant => {
|
||||
return {
|
||||
countryCode: "",
|
||||
displayName: "",
|
||||
domains: [],
|
||||
id: "",
|
||||
tenantId: "",
|
||||
};
|
||||
};
|
||||
|
||||
describe("test render", () => {
|
||||
it("renders with no directories", () => {
|
||||
const props = createBlankProps();
|
||||
|
||||
const wrapper = shallow(<DefaultDirectoryDropdownComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders with directories but no default", () => {
|
||||
const props = createBlankProps();
|
||||
const tenant1 = createBlankDirectory();
|
||||
tenant1.displayName = "Microsoft";
|
||||
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
|
||||
const tenant2 = createBlankDirectory();
|
||||
tenant1.displayName = "Macrohard";
|
||||
tenant1.tenantId = "asdfghjklzxcvbnm9876543210";
|
||||
props.directories = [tenant1, tenant2];
|
||||
|
||||
const wrapper = shallow(<DefaultDirectoryDropdownComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders with directories and default", () => {
|
||||
const props = createBlankProps();
|
||||
const tenant1 = createBlankDirectory();
|
||||
tenant1.displayName = "Microsoft";
|
||||
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
|
||||
const tenant2 = createBlankDirectory();
|
||||
tenant1.displayName = "Macrohard";
|
||||
tenant1.tenantId = "asdfghjklzxcvbnm9876543210";
|
||||
props.directories = [tenant1, tenant2];
|
||||
|
||||
props.defaultDirectoryId = "asdfghjklzxcvbnm9876543210";
|
||||
|
||||
const wrapper = shallow(<DefaultDirectoryDropdownComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders with directories and last visit default", () => {
|
||||
const props = createBlankProps();
|
||||
const tenant1 = createBlankDirectory();
|
||||
tenant1.displayName = "Microsoft";
|
||||
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
|
||||
const tenant2 = createBlankDirectory();
|
||||
tenant1.displayName = "Macrohard";
|
||||
tenant1.tenantId = "asdfghjklzxcvbnm9876543210";
|
||||
props.directories = [tenant1, tenant2];
|
||||
|
||||
props.defaultDirectoryId = "lastVisited";
|
||||
|
||||
const wrapper = shallow(<DefaultDirectoryDropdownComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("test function", () => {
|
||||
it("on default directory change", () => {
|
||||
const props = createBlankProps();
|
||||
const tenant1 = createBlankDirectory();
|
||||
tenant1.displayName = "Microsoft";
|
||||
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
|
||||
const tenant2 = createBlankDirectory();
|
||||
tenant1.displayName = "Macrohard";
|
||||
tenant1.tenantId = "asdfghjklzxcvbnm9876543210";
|
||||
props.directories = [tenant1, tenant2];
|
||||
props.defaultDirectoryId = "lastVisited";
|
||||
|
||||
const wrapper = mount(<DefaultDirectoryDropdownComponent {...props} />);
|
||||
|
||||
wrapper.find("div.defaultDirectoryDropdown").find("div.ms-Dropdown").simulate("click");
|
||||
expect(wrapper.exists("div.ms-Callout-main")).toBe(true);
|
||||
wrapper.find("button.ms-Dropdown-item").at(1).simulate("click");
|
||||
expect(props.onDefaultDirectoryChange).toBeCalled();
|
||||
expect(props.onDefaultDirectoryChange).toHaveBeenCalled();
|
||||
|
||||
wrapper.find("div.defaultDirectoryDropdown").find("div.ms-Dropdown").simulate("click");
|
||||
expect(wrapper.exists("div.ms-Callout-main")).toBe(true);
|
||||
wrapper.find("button.ms-Dropdown-item").at(0).simulate("click");
|
||||
expect(props.onDefaultDirectoryChange).toBeCalled();
|
||||
expect(props.onDefaultDirectoryChange).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,71 +0,0 @@
|
||||
/**
|
||||
* React component for Switch Directory
|
||||
*/
|
||||
|
||||
import { Dropdown, IDropdownOption, IDropdownProps } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import _ from "underscore";
|
||||
import { Tenant } from "../../../Contracts/DataModels";
|
||||
|
||||
export interface DefaultDirectoryDropdownProps {
|
||||
directories: Array<Tenant>;
|
||||
defaultDirectoryId: string;
|
||||
onDefaultDirectoryChange: (newDirectory: Tenant) => void;
|
||||
}
|
||||
|
||||
export class DefaultDirectoryDropdownComponent extends React.Component<DefaultDirectoryDropdownProps> {
|
||||
public static readonly lastVisitedKey: string = "lastVisited";
|
||||
|
||||
public render(): JSX.Element {
|
||||
const lastVisitedOption: IDropdownOption = {
|
||||
key: DefaultDirectoryDropdownComponent.lastVisitedKey,
|
||||
text: "Sign in to your last visited directory",
|
||||
};
|
||||
const directoryOptions: Array<IDropdownOption> = this.props.directories.map(
|
||||
(dirc): IDropdownOption => {
|
||||
return {
|
||||
key: dirc.tenantId,
|
||||
text: `${dirc.displayName}(${dirc.tenantId})`,
|
||||
};
|
||||
}
|
||||
);
|
||||
const dropDownOptions: Array<IDropdownOption> = [lastVisitedOption, ...directoryOptions];
|
||||
const dropDownProps: IDropdownProps = {
|
||||
label: "Set your default directory",
|
||||
options: dropDownOptions,
|
||||
defaultSelectedKey: this.props.defaultDirectoryId ? this.props.defaultDirectoryId : lastVisitedOption.key,
|
||||
onChange: this._onDropdownChange,
|
||||
className: "defaultDirectoryDropdown",
|
||||
};
|
||||
|
||||
return <Dropdown {...dropDownProps} />;
|
||||
}
|
||||
|
||||
private _onDropdownChange = (e: React.FormEvent<HTMLDivElement>, option?: IDropdownOption, index?: number): void => {
|
||||
if (!option || !option.key) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (option.key === this.props.defaultDirectoryId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (option.key === DefaultDirectoryDropdownComponent.lastVisitedKey) {
|
||||
this.props.onDefaultDirectoryChange({
|
||||
tenantId: option.key,
|
||||
countryCode: undefined,
|
||||
displayName: undefined,
|
||||
domains: [],
|
||||
id: undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedDirectory = _.find(this.props.directories, (d) => d.tenantId === option.key);
|
||||
if (!selectedDirectory) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onDefaultDirectoryChange(selectedDirectory);
|
||||
};
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import * as ko from "knockout";
|
||||
import * as React from "react";
|
||||
import { DirectoryListComponent, DirectoryListProps } from "./DirectoryListComponent";
|
||||
import { DefaultDirectoryDropdownComponent, DefaultDirectoryDropdownProps } from "./DefaultDirectoryDropdownComponent";
|
||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||
|
||||
export class DirectoryComponentAdapter implements ReactAdapter {
|
||||
public parameters: ko.Observable<number>;
|
||||
|
||||
constructor(
|
||||
private _dropdownProps: ko.Observable<DefaultDirectoryDropdownProps>,
|
||||
private _listProps: ko.Observable<DirectoryListProps>
|
||||
) {
|
||||
this._dropdownProps.subscribe(() => this.forceRender());
|
||||
this._listProps.subscribe(() => this.forceRender());
|
||||
this.parameters = ko.observable<number>(Date.now());
|
||||
}
|
||||
|
||||
public renderComponent(): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<div className="directoryDropdownContainer">
|
||||
<DefaultDirectoryDropdownComponent {...this._dropdownProps()} />
|
||||
</div>
|
||||
<div className="directoryDivider" />
|
||||
<div className="directoryListContainer">
|
||||
<DirectoryListComponent {...this._listProps()} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public forceRender(): void {
|
||||
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
import React from "react";
|
||||
import { shallow, mount } from "enzyme";
|
||||
import { DirectoryListComponent, DirectoryListProps } from "./DirectoryListComponent";
|
||||
import { Tenant } from "../../../Contracts/DataModels";
|
||||
|
||||
const createBlankProps = (): DirectoryListProps => {
|
||||
return {
|
||||
selectedDirectoryId: undefined,
|
||||
directories: [],
|
||||
onNewDirectorySelected: jest.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
const createBlankDirectory = (): Tenant => {
|
||||
return {
|
||||
countryCode: undefined,
|
||||
displayName: undefined,
|
||||
domains: [],
|
||||
id: undefined,
|
||||
tenantId: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
describe("test render", () => {
|
||||
it("renders with no directories", () => {
|
||||
const props = createBlankProps();
|
||||
|
||||
const wrapper = shallow(<DirectoryListComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders with directories and selected", () => {
|
||||
const props = createBlankProps();
|
||||
const tenant1 = createBlankDirectory();
|
||||
tenant1.displayName = "Microsoft";
|
||||
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
|
||||
const tenant2 = createBlankDirectory();
|
||||
tenant1.displayName = "Macrohard";
|
||||
tenant1.tenantId = "asdfghjklzxcvbnm9876543210";
|
||||
props.directories = [tenant1, tenant2];
|
||||
|
||||
props.selectedDirectoryId = "asdfghjklzxcvbnm9876543210";
|
||||
|
||||
const wrapper = shallow(<DirectoryListComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders with filters", () => {
|
||||
const props = createBlankProps();
|
||||
const tenant1 = createBlankDirectory();
|
||||
tenant1.displayName = "Microsoft";
|
||||
tenant1.tenantId = "1234567890";
|
||||
const tenant2 = createBlankDirectory();
|
||||
tenant1.displayName = "Macrohard";
|
||||
tenant1.tenantId = "9876543210";
|
||||
props.directories = [tenant1, tenant2];
|
||||
props.selectedDirectoryId = "9876543210";
|
||||
|
||||
const wrapper = mount(<DirectoryListComponent {...props} />);
|
||||
wrapper.find("input.ms-TextField-field").simulate("change", { target: { value: "Macro" } });
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("test function", () => {
|
||||
it("on new directory selected", () => {
|
||||
const props = createBlankProps();
|
||||
const tenant1 = createBlankDirectory();
|
||||
tenant1.displayName = "Microsoft";
|
||||
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
|
||||
props.directories = [tenant1];
|
||||
|
||||
const wrapper = mount(<DirectoryListComponent {...props} />);
|
||||
wrapper.find("button.directoryListButton").simulate("click");
|
||||
expect(props.onNewDirectorySelected).toBeCalled();
|
||||
expect(props.onNewDirectorySelected).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,126 +0,0 @@
|
||||
import {
|
||||
DefaultButton,
|
||||
IButtonProps,
|
||||
ITextFieldProps,
|
||||
List,
|
||||
ScrollablePane,
|
||||
Sticky,
|
||||
StickyPositionType,
|
||||
TextField,
|
||||
} from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import _ from "underscore";
|
||||
import { Tenant } from "../../../Contracts/DataModels";
|
||||
|
||||
export interface DirectoryListProps {
|
||||
directories: Array<Tenant>;
|
||||
selectedDirectoryId: string;
|
||||
onNewDirectorySelected: (newDirectory: Tenant) => void;
|
||||
}
|
||||
|
||||
export interface DirectoryListComponentState {
|
||||
filterText: string;
|
||||
}
|
||||
|
||||
// onRenderCell is not called when selectedDirectoryId changed, so add a selected state to force render
|
||||
interface ListTenant extends Tenant {
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
export class DirectoryListComponent extends React.Component<DirectoryListProps, DirectoryListComponentState> {
|
||||
constructor(props: DirectoryListProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
filterText: "",
|
||||
};
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
const { directories: originalItems, selectedDirectoryId } = this.props;
|
||||
const { filterText } = this.state;
|
||||
const filteredItems =
|
||||
originalItems && originalItems.length && filterText
|
||||
? originalItems.filter(
|
||||
(directory) =>
|
||||
directory.displayName &&
|
||||
directory.displayName.toLowerCase().indexOf(filterText && filterText.toLowerCase()) >= 0
|
||||
)
|
||||
: originalItems;
|
||||
const filteredItemsSelected = filteredItems.map((t) => {
|
||||
let tenant: ListTenant = t;
|
||||
tenant.selected = t.tenantId === selectedDirectoryId;
|
||||
return tenant;
|
||||
});
|
||||
|
||||
const textFieldProps: ITextFieldProps = {
|
||||
className: "directoryListFilterTextBox",
|
||||
placeholder: "Filter by directory name",
|
||||
onChange: this._onFilterChanged,
|
||||
ariaLabel: "Directory filter text box",
|
||||
};
|
||||
|
||||
// TODO: add magnify glass to search bar with onRenderSuffix
|
||||
return (
|
||||
<ScrollablePane data-is-scrollable="true">
|
||||
<Sticky stickyPosition={StickyPositionType.Header}>
|
||||
<TextField {...textFieldProps} />
|
||||
</Sticky>
|
||||
<List items={filteredItemsSelected} onRenderCell={this._onRenderCell} />
|
||||
</ScrollablePane>
|
||||
);
|
||||
}
|
||||
|
||||
private _onFilterChanged = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, text?: string): void => {
|
||||
this.setState({
|
||||
filterText: text,
|
||||
});
|
||||
};
|
||||
|
||||
private _onRenderCell = (directory: ListTenant): JSX.Element => {
|
||||
const buttonProps: IButtonProps = {
|
||||
disabled: directory.selected || false,
|
||||
className: "directoryListButton",
|
||||
onClick: this._onNewDirectoryClick,
|
||||
styles: {
|
||||
root: {
|
||||
backgroundColor: "transparent",
|
||||
height: "auto",
|
||||
borderBottom: "1px solid #ccc",
|
||||
padding: "1px 0",
|
||||
width: "100%",
|
||||
},
|
||||
rootDisabled: {
|
||||
backgroundColor: "#f1f1f8",
|
||||
},
|
||||
rootHovered: {
|
||||
backgroundColor: "rgba(85,179,255,.1)",
|
||||
},
|
||||
flexContainer: {
|
||||
height: "auto",
|
||||
justifyContent: "flex-start",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<DefaultButton {...buttonProps}>
|
||||
<div className="directoryListItem" data-is-focusable={true}>
|
||||
<div className="directoryListItemName">{directory.displayName}</div>
|
||||
<div className="directoryListItemId">{directory.tenantId}</div>
|
||||
</div>
|
||||
</DefaultButton>
|
||||
);
|
||||
};
|
||||
|
||||
private _onNewDirectoryClick = (e: React.MouseEvent<HTMLButtonElement>): void => {
|
||||
if (!e || !e.currentTarget) {
|
||||
return;
|
||||
}
|
||||
const buttonElement = e.currentTarget;
|
||||
const selectedDirectoryId = buttonElement.getElementsByClassName("directoryListItemId")[0].textContent;
|
||||
const selectedDirectory = _.find(this.props.directories, (d) => d.tenantId === selectedDirectoryId);
|
||||
|
||||
this.props.onNewDirectorySelected(selectedDirectory);
|
||||
};
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`test render renders with directories and default 1`] = `
|
||||
<Dropdown
|
||||
className="defaultDirectoryDropdown"
|
||||
defaultSelectedKey="asdfghjklzxcvbnm9876543210"
|
||||
label="Set your default directory"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"key": "lastVisited",
|
||||
"text": "Sign in to your last visited directory",
|
||||
},
|
||||
Object {
|
||||
"key": "asdfghjklzxcvbnm9876543210",
|
||||
"text": "Macrohard(asdfghjklzxcvbnm9876543210)",
|
||||
},
|
||||
Object {
|
||||
"key": "",
|
||||
"text": "()",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`test render renders with directories and last visit default 1`] = `
|
||||
<Dropdown
|
||||
className="defaultDirectoryDropdown"
|
||||
defaultSelectedKey="lastVisited"
|
||||
label="Set your default directory"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"key": "lastVisited",
|
||||
"text": "Sign in to your last visited directory",
|
||||
},
|
||||
Object {
|
||||
"key": "asdfghjklzxcvbnm9876543210",
|
||||
"text": "Macrohard(asdfghjklzxcvbnm9876543210)",
|
||||
},
|
||||
Object {
|
||||
"key": "",
|
||||
"text": "()",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`test render renders with directories but no default 1`] = `
|
||||
<Dropdown
|
||||
className="defaultDirectoryDropdown"
|
||||
defaultSelectedKey="lastVisited"
|
||||
label="Set your default directory"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"key": "lastVisited",
|
||||
"text": "Sign in to your last visited directory",
|
||||
},
|
||||
Object {
|
||||
"key": "asdfghjklzxcvbnm9876543210",
|
||||
"text": "Macrohard(asdfghjklzxcvbnm9876543210)",
|
||||
},
|
||||
Object {
|
||||
"key": "",
|
||||
"text": "()",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`test render renders with no directories 1`] = `
|
||||
<Dropdown
|
||||
className="defaultDirectoryDropdown"
|
||||
defaultSelectedKey="lastVisited"
|
||||
label="Set your default directory"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"key": "lastVisited",
|
||||
"text": "Sign in to your last visited directory",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
`;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 { loadMonaco, monaco } from "../../LazyMonaco";
|
||||
// import "./EditorReact.less";
|
||||
|
||||
interface EditorReactStates {
|
||||
showEditor: boolean;
|
||||
}
|
||||
export interface EditorReactProps {
|
||||
language: string;
|
||||
content: string;
|
||||
@@ -12,22 +17,26 @@ export interface EditorReactProps {
|
||||
theme?: string; // Monaco editor theme
|
||||
}
|
||||
|
||||
export class EditorReact extends React.Component<EditorReactProps> {
|
||||
export class EditorReact extends React.Component<EditorReactProps, EditorReactStates> {
|
||||
private rootNode: HTMLElement;
|
||||
private editor: monaco.editor.IStandaloneCodeEditor;
|
||||
private selectionListener: monaco.IDisposable;
|
||||
|
||||
public constructor(props: EditorReactProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showEditor: false,
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
this.createEditor(this.configureEditor.bind(this));
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(): boolean {
|
||||
// Prevents component re-rendering
|
||||
return false;
|
||||
public componentDidUpdate(previous: EditorReactProps) {
|
||||
if (this.props.content !== previous.content) {
|
||||
this.editor.setValue(this.props.content);
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
@@ -35,7 +44,12 @@ export class EditorReact extends React.Component<EditorReactProps> {
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -76,6 +90,12 @@ export class EditorReact extends React.Component<EditorReactProps> {
|
||||
this.rootNode.innerHTML = "";
|
||||
const monaco = await loadMonaco();
|
||||
createCallback(monaco.editor.create(this.rootNode, options));
|
||||
|
||||
if (this.rootNode.innerHTML) {
|
||||
this.setState({
|
||||
showEditor: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
defaultExperience: "NotebookViewer",
|
||||
isReadOnly: true,
|
||||
cellEditorType: "monaco",
|
||||
cellEditorType: "codemirror",
|
||||
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
|
||||
});
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
@import "../../../../less/Common/Constants.less";
|
||||
|
||||
.radioSwitchComponent {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
|
||||
&>span:nth-child(n+2) {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.caption {
|
||||
color: @BaseDark;
|
||||
padding-left: @SmallSpace;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/**
|
||||
* Horizontal switch component
|
||||
*/
|
||||
|
||||
import { Icon } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import { NormalizedEventKey } from "../../../Common/Constants";
|
||||
import "./RadioSwitchComponent.less";
|
||||
|
||||
export interface Choice {
|
||||
key: string;
|
||||
onSelect: () => void;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface RadioSwitchComponentProps {
|
||||
choices: Choice[];
|
||||
selectedKey: string;
|
||||
onSelectionKeyChange?: (newValue: string) => void;
|
||||
}
|
||||
|
||||
export class RadioSwitchComponent extends React.Component<RadioSwitchComponentProps> {
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<div className="radioSwitchComponent">
|
||||
{this.props.choices.map((choice: Choice) => (
|
||||
<span
|
||||
tabIndex={0}
|
||||
key={choice.key}
|
||||
onClick={() => this.onSelect(choice)}
|
||||
onKeyPress={(event) => this.onKeyPress(event, choice)}
|
||||
>
|
||||
<Icon iconName={this.props.selectedKey === choice.key ? "RadioBtnOn" : "RadioBtnOff"} />
|
||||
<span className="caption">{choice.label}</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private onSelect(choice: Choice): void {
|
||||
this.props.onSelectionKeyChange && this.props.onSelectionKeyChange(choice.key);
|
||||
choice.onSelect();
|
||||
}
|
||||
|
||||
private onKeyPress(event: React.KeyboardEvent<HTMLSpanElement>, choice: Choice): void {
|
||||
if (event.key === NormalizedEventKey.Enter || event.key === NormalizedEventKey.Space) {
|
||||
this.onSelect(choice);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* Generic abstract React component that senses its dimensions.
|
||||
* It updates its state and re-renders if dimensions change.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import * as ResizeSensor from "css-element-queries/src/ResizeSensor";
|
||||
|
||||
export abstract class ResizeSensorComponent<P, S> extends React.Component<P, S> {
|
||||
private isSensing: boolean = false;
|
||||
private resizeSensor: any;
|
||||
|
||||
public constructor(props: P) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected abstract onDimensionsChanged(width: number, height: number): void;
|
||||
protected abstract getSensorTarget(): HTMLElement;
|
||||
|
||||
public componentDidUpdate(): void {
|
||||
if (this.isSensing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bar = this.getSensorTarget();
|
||||
if (bar.clientWidth > 0 || bar.clientHeight > 0) {
|
||||
const oldPosition = bar.style.position;
|
||||
// TODO Find a better way to use constructor
|
||||
this.resizeSensor = new (ResizeSensor as any)(bar, () => {
|
||||
this.onDimensionsChanged(bar.clientWidth, bar.clientHeight);
|
||||
});
|
||||
this.isSensing = true;
|
||||
|
||||
// ResizeSensor.js sets position to 'relative' which makes the dropdown menu appear clipped.
|
||||
// Undoing doesn't seem to affect resize sensing functionality.
|
||||
bar.style.position = oldPosition;
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
if (!!this.resizeSensor) {
|
||||
this.resizeSensor.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,8 +38,6 @@ describe("SettingsComponent", () => {
|
||||
title: "Scale & Settings",
|
||||
tabPath: "",
|
||||
node: undefined,
|
||||
hashLocation: "settings",
|
||||
onUpdateTabsButtons: undefined,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -128,7 +126,6 @@ describe("SettingsComponent", () => {
|
||||
isDatabaseExpanded: undefined,
|
||||
isDatabaseShared: ko.computed(() => true),
|
||||
selectedSubnodeKind: undefined,
|
||||
selectDatabase: undefined,
|
||||
expandDatabase: undefined,
|
||||
collapseDatabase: undefined,
|
||||
loadCollections: undefined,
|
||||
|
||||
@@ -16,8 +16,9 @@ import { trace, traceFailure, traceStart, traceSuccess } from "../../../Shared/T
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
|
||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import Explorer from "../../Explorer";
|
||||
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
|
||||
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
|
||||
import "./SettingsComponent.less";
|
||||
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
|
||||
@@ -109,6 +110,7 @@ export interface SettingsComponentState {
|
||||
|
||||
initialNotification: DataModels.Notification;
|
||||
selectedTab: SettingsV2TabTypes;
|
||||
offerLoaded: boolean;
|
||||
}
|
||||
|
||||
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 database: ViewModels.Database;
|
||||
private offer: DataModels.Offer;
|
||||
private container: Explorer;
|
||||
private changeFeedPolicyVisible: boolean;
|
||||
private isFixedContainer: 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;
|
||||
if (this.isCollectionSettingsTab) {
|
||||
this.collection = this.props.settingsTab.collection as ViewModels.Collection;
|
||||
this.container = this.collection?.container;
|
||||
this.offer = this.collection?.offer();
|
||||
this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl();
|
||||
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);
|
||||
} else {
|
||||
this.database = this.props.settingsTab.database;
|
||||
this.container = this.database?.container;
|
||||
this.offer = this.database?.offer();
|
||||
}
|
||||
|
||||
@@ -196,6 +195,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
|
||||
initialNotification: undefined,
|
||||
selectedTab: SettingsV2TabTypes.ScaleTab,
|
||||
offerLoaded: !!this.offer,
|
||||
};
|
||||
|
||||
this.saveSettingsButton = {
|
||||
@@ -217,18 +217,19 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
if (this.isCollectionSettingsTab) {
|
||||
this.refreshIndexTransformationProgress();
|
||||
this.loadMongoIndexes();
|
||||
this.loadCollectionOffer();
|
||||
}
|
||||
|
||||
this.setAutoPilotStates();
|
||||
this.setBaseline();
|
||||
if (this.props.settingsTab.isActive()) {
|
||||
this.props.settingsTab.getContainer().onUpdateTabsButtons(this.getTabsButtons());
|
||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(): void {
|
||||
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;
|
||||
|
||||
public shouldShowKeyspaceSharedThroughputMessage = (): boolean =>
|
||||
this.container && userContext.apiType === "Cassandra" && hasDatabaseSharedThroughput(this.collection);
|
||||
userContext.apiType === "Cassandra" && hasDatabaseSharedThroughput(this.collection);
|
||||
|
||||
public hasConflictResolution = (): boolean =>
|
||||
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[] => {
|
||||
let finalIndexes: MongoIndex[] = [];
|
||||
this.state.currentMongoIndexes?.map((mongoIndex: MongoIndex, index: number) => {
|
||||
@@ -883,7 +912,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
const scaleComponentProps: ScaleComponentProps = {
|
||||
collection: this.collection,
|
||||
database: this.database,
|
||||
container: this.container,
|
||||
isFixedContainer: this.isFixedContainer,
|
||||
onThroughputChange: this.onThroughputChange,
|
||||
throughput: this.state.throughput,
|
||||
@@ -909,9 +937,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.state.offerLoaded) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const subSettingsComponentProps: SubSettingsComponentProps = {
|
||||
collection: this.collection,
|
||||
container: this.container,
|
||||
isAnalyticalStorageEnabled: this.isAnalyticalStorageEnabled,
|
||||
changeFeedPolicyVisible: this.changeFeedPolicyVisible,
|
||||
timeToLive: this.state.timeToLive,
|
||||
@@ -964,7 +995,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
|
||||
const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
|
||||
collection: this.collection,
|
||||
container: this.container,
|
||||
conflictResolutionPolicyMode: this.state.conflictResolutionPolicyMode,
|
||||
conflictResolutionPolicyModeBaseline: this.state.conflictResolutionPolicyModeBaseline,
|
||||
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 React from "react";
|
||||
import { ConflictResolutionComponentProps, ConflictResolutionComponent } from "./ConflictResolutionComponent";
|
||||
import { container, collection } from "../TestUtils";
|
||||
import * as DataModels from "../../../../Contracts/DataModels";
|
||||
import { collection } from "../TestUtils";
|
||||
import { ConflictResolutionComponent, ConflictResolutionComponentProps } from "./ConflictResolutionComponent";
|
||||
|
||||
describe("ConflictResolutionComponent", () => {
|
||||
const baseProps: ConflictResolutionComponentProps = {
|
||||
collection: collection,
|
||||
container: container,
|
||||
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode.Custom,
|
||||
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode.Custom,
|
||||
onConflictResolutionPolicyModeChange: () => {
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import { ChoiceGroup, IChoiceGroupOption, ITextFieldProps, Stack, TextField } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import * as ViewModels from "../../../../Contracts/ViewModels";
|
||||
import * as DataModels from "../../../../Contracts/DataModels";
|
||||
import Explorer from "../../../Explorer";
|
||||
import * as ViewModels from "../../../../Contracts/ViewModels";
|
||||
import {
|
||||
getTextFieldStyles,
|
||||
conflictResolutionLwwTooltip,
|
||||
conflictResolutionCustomToolTip,
|
||||
subComponentStackProps,
|
||||
conflictResolutionLwwTooltip,
|
||||
getChoiceGroupStyles,
|
||||
getTextFieldStyles,
|
||||
subComponentStackProps,
|
||||
} from "../SettingsRenderUtils";
|
||||
import { TextField, ITextFieldProps, Stack, IChoiceGroupOption, ChoiceGroup } from "@fluentui/react";
|
||||
import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
|
||||
import { isDirty } from "../SettingsUtils";
|
||||
import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
|
||||
|
||||
export interface ConflictResolutionComponentProps {
|
||||
collection: ViewModels.Collection;
|
||||
container: Explorer;
|
||||
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
||||
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
|
||||
onConflictResolutionPolicyModeChange: (newMode: DataModels.ConflictResolutionMode) => void;
|
||||
|
||||
@@ -7,20 +7,17 @@ import * as SharedConstants from "../../../../Shared/Constants";
|
||||
import { updateUserContext } from "../../../../UserContext";
|
||||
import Explorer from "../../../Explorer";
|
||||
import { throughputUnit } from "../SettingsRenderUtils";
|
||||
import { collection, container } from "../TestUtils";
|
||||
import { collection } from "../TestUtils";
|
||||
import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent";
|
||||
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
|
||||
|
||||
describe("ScaleComponent", () => {
|
||||
const nonNationalCloudContainer = new Explorer();
|
||||
nonNationalCloudContainer.isRunningOnNationalCloud = () => false;
|
||||
|
||||
const targetThroughput = 6000;
|
||||
|
||||
const baseProps: ScaleComponentProps = {
|
||||
collection: collection,
|
||||
database: undefined,
|
||||
container: container,
|
||||
isFixedContainer: false,
|
||||
onThroughputChange: () => {
|
||||
return;
|
||||
@@ -111,7 +108,7 @@ describe("ScaleComponent", () => {
|
||||
let scaleComponent = new ScaleComponent(baseProps);
|
||||
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)");
|
||||
|
||||
let newProps = { ...baseProps, container: nonNationalCloudContainer };
|
||||
let newProps = { ...baseProps };
|
||||
scaleComponent = new ScaleComponent(newProps);
|
||||
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)");
|
||||
|
||||
@@ -124,7 +121,7 @@ describe("ScaleComponent", () => {
|
||||
let scaleComponent = new ScaleComponent(baseProps);
|
||||
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true);
|
||||
|
||||
const newProps = { ...baseProps, container: nonNationalCloudContainer };
|
||||
const newProps = { ...baseProps };
|
||||
scaleComponent = new ScaleComponent(newProps);
|
||||
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true);
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as ViewModels from "../../../../Contracts/ViewModels";
|
||||
import * as SharedConstants from "../../../../Shared/Constants";
|
||||
import { userContext } from "../../../../UserContext";
|
||||
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
|
||||
import Explorer from "../../../Explorer";
|
||||
import { isRunningOnNationalCloud } from "../../../../Utils/CloudUtils";
|
||||
import {
|
||||
getTextFieldStyles,
|
||||
getThroughputApplyLongDelayMessage,
|
||||
@@ -23,7 +23,6 @@ import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents
|
||||
export interface ScaleComponentProps {
|
||||
collection: ViewModels.Collection;
|
||||
database: ViewModels.Database;
|
||||
container: Explorer;
|
||||
isFixedContainer: boolean;
|
||||
onThroughputChange: (newThroughput: number) => void;
|
||||
throughput: number;
|
||||
@@ -109,11 +108,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||
};
|
||||
|
||||
public canThroughputExceedMaximumValue = (): boolean => {
|
||||
return (
|
||||
!this.props.isFixedContainer &&
|
||||
configContext.platform === Platform.Portal &&
|
||||
!this.props.container.isRunningOnNationalCloud()
|
||||
);
|
||||
return !this.props.isFixedContainer && configContext.platform === Platform.Portal && !isRunningOnNationalCloud();
|
||||
};
|
||||
|
||||
public getInitialNotificationElement = (): JSX.Element => {
|
||||
@@ -202,10 +197,12 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||
}
|
||||
|
||||
private getFreeTierInfoMessage(): JSX.Element {
|
||||
const freeTierLimits = SharedConstants.FreeTierLimits;
|
||||
return (
|
||||
<Text>
|
||||
With free tier, you will get the first 400 RU/s and 5 GB of storage in this account for free. To keep your
|
||||
account free, keep the total RU/s across all resources in the account to 400 RU/s.
|
||||
With free tier, you will get the first {freeTierLimits.RU} RU/s and {freeTierLimits.Storage} GB of storage in
|
||||
this account for free. To keep your account free, keep the total RU/s across all resources in the account to{" "}
|
||||
{freeTierLimits.RU} RU/s.
|
||||
<Link
|
||||
href="https://docs.microsoft.com/en-us/azure/cosmos-db/understand-your-bill#billing-examples-with-free-tier-accounts"
|
||||
target="_blank"
|
||||
|
||||
@@ -4,14 +4,12 @@ import { DatabaseAccount } from "../../../../Contracts/DataModels";
|
||||
import { updateUserContext } from "../../../../UserContext";
|
||||
import Explorer from "../../../Explorer";
|
||||
import { ChangeFeedPolicyState, GeospatialConfigType, TtlOff, TtlOn, TtlOnNoDefault, TtlType } from "../SettingsUtils";
|
||||
import { collection, container } from "../TestUtils";
|
||||
import { collection } from "../TestUtils";
|
||||
import { SubSettingsComponent, SubSettingsComponentProps } from "./SubSettingsComponent";
|
||||
|
||||
describe("SubSettingsComponent", () => {
|
||||
const baseProps: SubSettingsComponentProps = {
|
||||
collection: collection,
|
||||
container: container,
|
||||
|
||||
timeToLive: TtlType.On,
|
||||
timeToLiveBaseline: TtlType.On,
|
||||
onTtlChange: () => {
|
||||
|
||||
@@ -2,7 +2,6 @@ import { ChoiceGroup, IChoiceGroupOption, Label, Link, MessageBar, Stack, Text,
|
||||
import * as React from "react";
|
||||
import * as ViewModels from "../../../../Contracts/ViewModels";
|
||||
import { userContext } from "../../../../UserContext";
|
||||
import Explorer from "../../../Explorer";
|
||||
import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
||||
import {
|
||||
changeFeedPolicyToolTip,
|
||||
@@ -28,8 +27,6 @@ import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
|
||||
|
||||
export interface SubSettingsComponentProps {
|
||||
collection: ViewModels.Collection;
|
||||
container: Explorer;
|
||||
|
||||
timeToLive: TtlType;
|
||||
timeToLiveBaseline: TtlType;
|
||||
|
||||
|
||||
@@ -155,7 +155,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
this.state = {
|
||||
spendAckChecked: this.props.spendAckChecked,
|
||||
exceedFreeTierThroughput:
|
||||
this.props.isFreeTierAccount && !this.props.isAutoPilotSelected && this.props.throughput > 400,
|
||||
this.props.isFreeTierAccount &&
|
||||
!this.props.isAutoPilotSelected &&
|
||||
this.props.throughput > SharedConstants.FreeTierLimits.RU,
|
||||
};
|
||||
|
||||
this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep;
|
||||
@@ -441,7 +443,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
if (this.overrideWithAutoPilotSettings()) {
|
||||
this.props.onMaxAutoPilotThroughputChange(newThroughput);
|
||||
} else {
|
||||
this.setState({ exceedFreeTierThroughput: this.props.isFreeTierAccount && newThroughput > 400 });
|
||||
this.setState({
|
||||
exceedFreeTierThroughput: this.props.isFreeTierAccount && newThroughput > SharedConstants.FreeTierLimits.RU,
|
||||
});
|
||||
this.props.onThroughputChange(newThroughput);
|
||||
}
|
||||
};
|
||||
@@ -581,9 +585,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}
|
||||
styles={messageBarStyles}
|
||||
>
|
||||
{
|
||||
"Billing will apply if you provision more than 400 RU/s of manual throughput, or if the resource scales beyond 400 RU/s with autoscale."
|
||||
}
|
||||
{`Billing will apply if you provision more than ${SharedConstants.FreeTierLimits.RU} RU/s of manual throughput, or if the resource scales beyond ${SharedConstants.FreeTierLimits.RU} RU/s with autoscale.`}
|
||||
</MessageBar>
|
||||
)}
|
||||
{this.props.getThroughputWarningMessage() && (
|
||||
|
||||
@@ -36,7 +36,6 @@ describe("SettingsUtils", () => {
|
||||
isDatabaseExpanded: ko.observable(false),
|
||||
isDatabaseShared: ko.computed(() => true),
|
||||
selectedSubnodeKind: ko.observable(undefined),
|
||||
selectDatabase: undefined,
|
||||
expandDatabase: undefined,
|
||||
collapseDatabase: undefined,
|
||||
loadCollections: undefined,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ import { ThroughputInput } from "./ThroughputInput";
|
||||
const props = {
|
||||
isDatabase: false,
|
||||
showFreeTierExceedThroughputTooltip: true,
|
||||
isSharded: false,
|
||||
isSharded: true,
|
||||
setThroughputValue: () => jest.fn(),
|
||||
setIsAutoscale: () => jest.fn(),
|
||||
onCostAcknowledgeChange: () => jest.fn(),
|
||||
|
||||
@@ -41,9 +41,16 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
throughputHeaderText = AutoPilotUtils.getAutoPilotHeaderText().toLocaleLowerCase();
|
||||
} else {
|
||||
const minRU: string = SharedConstants.CollectionCreation.DefaultCollectionRUs400.toLocaleString();
|
||||
const maxRU: string = userContext.isTryCosmosDBSubscription
|
||||
? Constants.TryCosmosExperience.maxRU.toLocaleString()
|
||||
: "unlimited";
|
||||
|
||||
let maxRU: string;
|
||||
if (userContext.isTryCosmosDBSubscription) {
|
||||
maxRU = Constants.TryCosmosExperience.maxRU.toLocaleString();
|
||||
} else if (!isSharded) {
|
||||
maxRU = "10000";
|
||||
} else {
|
||||
maxRU = "unlimited";
|
||||
}
|
||||
|
||||
throughputHeaderText = `throughput (${minRU} - ${maxRU} RU/s)`;
|
||||
}
|
||||
return `${isDatabase ? "Database" : getCollectionName()} ${throughputHeaderText}`;
|
||||
@@ -187,9 +194,8 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
<TooltipHost
|
||||
directionalHint={DirectionalHint.topLeftEdge}
|
||||
content={
|
||||
showFreeTierExceedThroughputTooltip &&
|
||||
throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs400
|
||||
? "The first 400 RU/s in this account are free. Billing will apply to any throughput beyond 400 RU/s."
|
||||
showFreeTierExceedThroughputTooltip && throughput > SharedConstants.FreeTierLimits.RU
|
||||
? `The first ${SharedConstants.FreeTierLimits.RU} RU/s in this account are free. Billing will apply to any throughput beyond ${SharedConstants.FreeTierLimits.RU} RU/s.`
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
|
||||
@@ -1,308 +0,0 @@
|
||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||
import * as ko from "knockout";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import ThroughputInputComponentAutoscaleV3 from "./ThroughputInputComponentAutoscaleV3.html";
|
||||
import { KeyCodes } from "../../../Common/Constants";
|
||||
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
|
||||
|
||||
import { userContext } from "../../../UserContext";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
/**
|
||||
* 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() > 400
|
||||
);
|
||||
|
||||
this.showFreeTierExceedThroughputWarning = ko.pureComputed<boolean>(
|
||||
() => !!this.freeTierExceedThroughputWarning() && this.value() > 400
|
||||
);
|
||||
}
|
||||
|
||||
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`] = `
|
||||
<ThroughputInput
|
||||
isDatabase={false}
|
||||
isSharded={false}
|
||||
isSharded={true}
|
||||
onCostAcknowledgeChange={[Function]}
|
||||
setIsAutoscale={[Function]}
|
||||
setThroughputValue={[Function]}
|
||||
|
||||
@@ -58,7 +58,7 @@ export interface TreeComponentProps {
|
||||
export class TreeComponent extends React.Component<TreeComponentProps> {
|
||||
public render(): JSX.Element {
|
||||
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} />
|
||||
</div>
|
||||
);
|
||||
@@ -172,6 +172,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
||||
className={`${this.props.node.className || ""} main${generation} nodeItem ${showSelected ? "selected" : ""}`}
|
||||
onClick={(event: React.MouseEvent<HTMLDivElement>) => this.onNodeClick(event, node)}
|
||||
onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) => this.onNodeKeyPress(event, node)}
|
||||
role="treeitem"
|
||||
>
|
||||
<div
|
||||
className={`treeNodeHeader ${this.state.isMenuShowing ? "showingMenu" : ""}`}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
exports[`TreeComponent renders a simple tree 1`] = `
|
||||
<div
|
||||
className="treeComponent tree"
|
||||
role="tree"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
generation={0}
|
||||
@@ -37,6 +38,7 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
|
||||
className=" main2 nodeItem "
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
role="treeitem"
|
||||
>
|
||||
<div
|
||||
className="treeNodeHeader "
|
||||
@@ -137,6 +139,7 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
|
||||
className="nodeClassname main12 nodeItem "
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
role="treeitem"
|
||||
>
|
||||
<div
|
||||
className="treeNodeHeader "
|
||||
@@ -285,6 +288,7 @@ exports[`TreeNodeComponent renders loading icon 1`] = `
|
||||
className=" main2 nodeItem "
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
role="treeitem"
|
||||
>
|
||||
<div
|
||||
className="treeNodeHeader "
|
||||
@@ -356,6 +360,7 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
|
||||
className="nodeClassname main12 nodeItem "
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
role="treeitem"
|
||||
>
|
||||
<div
|
||||
className="treeNodeHeader "
|
||||
@@ -523,6 +528,7 @@ exports[`TreeNodeComponent renders unsorted children by default 1`] = `
|
||||
className=" main2 nodeItem "
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
role="treeitem"
|
||||
>
|
||||
<div
|
||||
className="treeNodeHeader "
|
||||
|
||||
@@ -2,23 +2,22 @@ jest.mock("../Graph/GraphExplorerComponent/GremlinClient");
|
||||
jest.mock("../../Common/dataAccess/createCollection");
|
||||
jest.mock("../../Common/dataAccess/createDocument");
|
||||
import * as ko from "knockout";
|
||||
import Q from "q";
|
||||
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||
import { DatabaseAccount } from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { updateUserContext } from "../../UserContext";
|
||||
import Explorer from "../Explorer";
|
||||
import { useDatabases } from "../useDatabases";
|
||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||
|
||||
describe("ContainerSampleGenerator", () => {
|
||||
const createExplorerStub = (database: ViewModels.Database): Explorer => {
|
||||
const explorerStub = {} as Explorer;
|
||||
explorerStub.databases = ko.observableArray<ViewModels.Database>([database]);
|
||||
explorerStub.canExceedMaximumValue = ko.computed<boolean>(() => false);
|
||||
explorerStub.findDatabaseWithId = () => database;
|
||||
explorerStub.refreshAllDatabases = () => Q.resolve();
|
||||
return explorerStub;
|
||||
};
|
||||
let explorerStub: Explorer;
|
||||
|
||||
beforeAll(() => {
|
||||
explorerStub = {
|
||||
refreshAllDatabases: () => {},
|
||||
} as Explorer;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
(createDocument as jest.Mock).mockResolvedValue(undefined);
|
||||
@@ -60,8 +59,7 @@ describe("ContainerSampleGenerator", () => {
|
||||
loadCollections: () => {},
|
||||
} as ViewModels.Database;
|
||||
database.findCollectionWithId = () => collection;
|
||||
|
||||
const explorerStub = createExplorerStub(database);
|
||||
useDatabases.getState().addDatabases([database]);
|
||||
|
||||
const generator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub);
|
||||
generator.setData(sampleData);
|
||||
@@ -109,8 +107,8 @@ describe("ContainerSampleGenerator", () => {
|
||||
} as ViewModels.Database;
|
||||
database.findCollectionWithId = () => collection;
|
||||
collection.databaseId = database.id();
|
||||
useDatabases.getState().addDatabases([database]);
|
||||
|
||||
const explorerStub = createExplorerStub(database);
|
||||
updateUserContext({
|
||||
databaseAccount: {
|
||||
properties: {
|
||||
@@ -127,7 +125,6 @@ describe("ContainerSampleGenerator", () => {
|
||||
|
||||
it("should not create any sample for Mongo API account", async () => {
|
||||
const experience = "Sample generation not supported for this API Mongo";
|
||||
const explorerStub = createExplorerStub(undefined);
|
||||
updateUserContext({
|
||||
databaseAccount: {
|
||||
properties: {
|
||||
@@ -142,7 +139,6 @@ describe("ContainerSampleGenerator", () => {
|
||||
|
||||
it("should not create any sample for Table API account", async () => {
|
||||
const experience = "Sample generation not supported for this API Tables";
|
||||
const explorerStub = createExplorerStub(undefined);
|
||||
updateUserContext({
|
||||
databaseAccount: {
|
||||
properties: {
|
||||
@@ -164,7 +160,6 @@ describe("ContainerSampleGenerator", () => {
|
||||
},
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
const explorerStub = createExplorerStub(undefined);
|
||||
// Rejects with error that contains 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 Explorer from "../Explorer";
|
||||
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
|
||||
import { useDatabases } from "../useDatabases";
|
||||
|
||||
interface SampleDataFile extends DataModels.CreateCollectionParams {
|
||||
data: any[];
|
||||
@@ -59,7 +60,7 @@ export class ContainerSampleGenerator {
|
||||
|
||||
await createCollection(createRequest);
|
||||
await this.container.refreshAllDatabases();
|
||||
const database = this.container.findDatabaseWithId(this.sampleDataFile.databaseId);
|
||||
const database = useDatabases.getState().findDatabaseWithId(this.sampleDataFile.databaseId);
|
||||
if (!database) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as ko from "knockout";
|
||||
import * as sinon from "sinon";
|
||||
import { Collection, Database } from "../../Contracts/ViewModels";
|
||||
import Explorer from "../Explorer";
|
||||
import { useDatabases } from "../useDatabases";
|
||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||
import { DataSamplesUtil } from "./DataSamplesUtil";
|
||||
|
||||
@@ -16,8 +17,8 @@ describe("DataSampleUtils", () => {
|
||||
collections: ko.observableArray<Collection>([collection]),
|
||||
} as Database;
|
||||
const explorer = {} as Explorer;
|
||||
explorer.databases = ko.observableArray<Database>([database]);
|
||||
explorer.showOkModalDialog = () => {};
|
||||
useDatabases.getState().addDatabases([database]);
|
||||
const dataSamplesUtil = new DataSamplesUtil(explorer);
|
||||
|
||||
const fakeGenerator = sinon.createStubInstance<ContainerSampleGenerator>(ContainerSampleGenerator as any);
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||
import Explorer from "../Explorer";
|
||||
import { useDatabases } from "../useDatabases";
|
||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||
|
||||
export class DataSamplesUtil {
|
||||
@@ -17,7 +18,7 @@ export class DataSamplesUtil {
|
||||
|
||||
const databaseName = generator.getDatabaseId();
|
||||
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.`;
|
||||
this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, 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 StorageUtility from "../../../Shared/StorageUtility";
|
||||
import { TabComponent } from "../../Controls/Tabs/TabComponent";
|
||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/ConsoleData";
|
||||
import GraphTab from "../../Tabs/GraphTab";
|
||||
import * as D3ForceGraph from "./D3ForceGraph";
|
||||
import { GraphData } from "./GraphData";
|
||||
|
||||
@@ -18,7 +18,7 @@ import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Ut
|
||||
import { EditorReact } from "../../Controls/Editor/EditorReact";
|
||||
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
|
||||
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 { ArraysByKeyCache } from "./ArraysByKeyCache";
|
||||
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 { GraphData, GremlinEdge, GremlinVertex } from "./GraphData";
|
||||
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 IN_E_MATCHER = "g\\.V\\(.*\\).inE\\(\\).*\\.as\\('e'\\).outV\\(\\)\\.as\\('v'\\)\\.select\\('e', *'v'\\)";
|
||||
|
||||
|
||||
@@ -5,21 +5,26 @@
|
||||
*/
|
||||
|
||||
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 { GraphExplorer } from "./GraphExplorer";
|
||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import CheckIcon from "../../../../images/check.svg";
|
||||
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 { 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 {
|
||||
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
|
||||
* and update any knockout observables passed from the parent.
|
||||
*/
|
||||
import { CommandBar, ICommandBarItemProps } from "@fluentui/react";
|
||||
import * as ko from "knockout";
|
||||
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||
import create, { UseStore } from "zustand";
|
||||
import { StyleConstants } from "../../../Common/Constants";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import Explorer from "../../Explorer";
|
||||
import { useSelectedNode } from "../../useSelectedNode";
|
||||
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
||||
import * as CommandBarUtil from "./CommandBarUtil";
|
||||
|
||||
export class CommandBarComponentAdapter implements ReactAdapter {
|
||||
public parameters: ko.Observable<number>;
|
||||
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()));
|
||||
}
|
||||
interface Props {
|
||||
container: Explorer;
|
||||
}
|
||||
|
||||
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 { AuthType } from "../../../AuthType";
|
||||
import { DatabaseAccount } from "../../../Contracts/DataModels";
|
||||
import { CollectionBase } from "../../../Contracts/ViewModels";
|
||||
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
||||
import { updateUserContext } from "../../../UserContext";
|
||||
import Explorer from "../../Explorer";
|
||||
import NotebookManager from "../../Notebook/NotebookManager";
|
||||
import { useSelectedNode } from "../../useSelectedNode";
|
||||
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
||||
|
||||
describe("CommandBarComponentButtonFactory tests", () => {
|
||||
let mockExplorer: Explorer;
|
||||
|
||||
afterEach(() => useSelectedNode.getState().setSelectedNode(undefined));
|
||||
|
||||
describe("Enable Azure Synapse Link Button", () => {
|
||||
const enableAzureSynapseLinkBtnLabel = "Enable Azure Synapse Link";
|
||||
const selectedNodeState = useSelectedNode.getState();
|
||||
|
||||
beforeAll(() => {
|
||||
mockExplorer = {} as Explorer;
|
||||
mockExplorer.addCollectionText = ko.observable("mockText");
|
||||
updateUserContext({
|
||||
databaseAccount: {
|
||||
properties: {
|
||||
@@ -23,19 +27,13 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
},
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
|
||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||
mockExplorer.isRunningOnNationalCloud = () => false;
|
||||
});
|
||||
|
||||
it("Account is not serverless - button should be visible", () => {
|
||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const enableAzureSynapseLinkBtn = buttons.find(
|
||||
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
|
||||
);
|
||||
@@ -43,9 +41,14 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
it("Account is serverless - button should be hidden", () => {
|
||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
||||
updateUserContext({
|
||||
databaseAccount: {
|
||||
properties: {
|
||||
capabilities: [{ name: "EnableServerless" }],
|
||||
},
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const enableAzureSynapseLinkBtn = buttons.find(
|
||||
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
|
||||
);
|
||||
@@ -55,11 +58,12 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
|
||||
describe("Enable notebook button", () => {
|
||||
const enableNotebookBtnLabel = "Enable Notebooks (Preview)";
|
||||
const selectedNodeState = useSelectedNode.getState();
|
||||
|
||||
beforeAll(() => {
|
||||
mockExplorer = {} as Explorer;
|
||||
mockExplorer.addCollectionText = ko.observable("mockText");
|
||||
updateUserContext({
|
||||
portalEnv: "prod",
|
||||
databaseAccount: {
|
||||
properties: {
|
||||
capabilities: [{ name: "EnableTable" }],
|
||||
@@ -67,19 +71,19 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
});
|
||||
|
||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
||||
afterEach(() => {
|
||||
updateUserContext({
|
||||
portalEnv: "prod",
|
||||
});
|
||||
});
|
||||
|
||||
it("Notebooks is already enabled - button should be hidden", () => {
|
||||
mockExplorer.isNotebookEnabled = 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);
|
||||
expect(enableNotebookBtn).toBeUndefined();
|
||||
});
|
||||
@@ -87,9 +91,11 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
it("Account is running on one of the national clouds - button should be hidden", () => {
|
||||
mockExplorer.isNotebookEnabled = 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);
|
||||
expect(enableNotebookBtn).toBeUndefined();
|
||||
});
|
||||
@@ -97,9 +103,8 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
it("Notebooks is not enabled but is available - button should be shown and enabled", () => {
|
||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||
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);
|
||||
expect(enableNotebookBtn).toBeDefined();
|
||||
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", () => {
|
||||
mockExplorer.isNotebookEnabled = 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);
|
||||
expect(enableNotebookBtn).toBeDefined();
|
||||
expect(enableNotebookBtn.disabled).toBe(true);
|
||||
@@ -123,10 +127,10 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
|
||||
describe("Open Mongo Shell button", () => {
|
||||
const openMongoShellBtnLabel = "Open Mongo Shell";
|
||||
const selectedNodeState = useSelectedNode.getState();
|
||||
|
||||
beforeAll(() => {
|
||||
mockExplorer = {} as Explorer;
|
||||
mockExplorer.addCollectionText = ko.observable("mockText");
|
||||
updateUserContext({
|
||||
databaseAccount: {
|
||||
properties: {
|
||||
@@ -134,11 +138,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
},
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
|
||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
||||
mockExplorer.isShellEnabled = ko.observable(true);
|
||||
});
|
||||
|
||||
@@ -154,7 +154,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
|
||||
|
||||
mockExplorer.isShellEnabled = ko.observable(true);
|
||||
});
|
||||
|
||||
@@ -162,21 +162,23 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
updateUserContext({
|
||||
apiType: "SQL",
|
||||
});
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||
expect(openMongoShellBtn).toBeUndefined();
|
||||
});
|
||||
|
||||
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);
|
||||
expect(openMongoShellBtn).toBeUndefined();
|
||||
});
|
||||
|
||||
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);
|
||||
expect(openMongoShellBtn).toBeUndefined();
|
||||
});
|
||||
@@ -184,7 +186,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
it("Notebooks is not enabled and is available - button should be hidden", () => {
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||
expect(openMongoShellBtn).toBeUndefined();
|
||||
});
|
||||
@@ -192,7 +194,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||
expect(openMongoShellBtn).toBeDefined();
|
||||
expect(openMongoShellBtn.disabled).toBe(false);
|
||||
@@ -203,7 +205,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
mockExplorer.isNotebookEnabled = 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);
|
||||
expect(openMongoShellBtn).toBeDefined();
|
||||
expect(openMongoShellBtn.disabled).toBe(false);
|
||||
@@ -215,7 +217,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||
mockExplorer.isShellEnabled = ko.observable(false);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||
expect(openMongoShellBtn).toBeUndefined();
|
||||
});
|
||||
@@ -223,10 +225,10 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
|
||||
describe("Open Cassandra Shell button", () => {
|
||||
const openCassandraShellBtnLabel = "Open Cassandra Shell";
|
||||
const selectedNodeState = useSelectedNode.getState();
|
||||
|
||||
beforeAll(() => {
|
||||
mockExplorer = {} as Explorer;
|
||||
mockExplorer.addCollectionText = ko.observable("mockText");
|
||||
updateUserContext({
|
||||
databaseAccount: {
|
||||
properties: {
|
||||
@@ -235,10 +237,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||
|
||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -251,7 +249,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
|
||||
});
|
||||
|
||||
it("Cassandra Api not available - button should be hidden", () => {
|
||||
@@ -263,21 +260,23 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
console.log(mockExplorer);
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||
expect(openCassandraShellBtn).toBeUndefined();
|
||||
});
|
||||
|
||||
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);
|
||||
expect(openCassandraShellBtn).toBeUndefined();
|
||||
});
|
||||
|
||||
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);
|
||||
expect(openCassandraShellBtn).toBeUndefined();
|
||||
});
|
||||
@@ -285,7 +284,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
it("Notebooks is not enabled and is available - button should be shown and enabled", () => {
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||
expect(openCassandraShellBtn).toBeUndefined();
|
||||
});
|
||||
@@ -293,7 +292,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||
expect(openCassandraShellBtn).toBeDefined();
|
||||
expect(openCassandraShellBtn.disabled).toBe(false);
|
||||
@@ -304,7 +303,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
mockExplorer.isNotebookEnabled = 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);
|
||||
expect(openCassandraShellBtn).toBeDefined();
|
||||
expect(openCassandraShellBtn.disabled).toBe(false);
|
||||
@@ -315,10 +314,10 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
describe("GitHub buttons", () => {
|
||||
const connectToGitHubBtnLabel = "Connect to GitHub";
|
||||
const manageGitHubSettingsBtnLabel = "Manage GitHub settings";
|
||||
const selectedNodeState = useSelectedNode.getState();
|
||||
|
||||
beforeAll(() => {
|
||||
mockExplorer = {} as Explorer;
|
||||
mockExplorer.addCollectionText = ko.observable("mockText");
|
||||
updateUserContext({
|
||||
databaseAccount: {
|
||||
properties: {
|
||||
@@ -328,13 +327,10 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
|
||||
|
||||
mockExplorer.notebookManager = new NotebookManager();
|
||||
mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined);
|
||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
||||
});
|
||||
|
||||
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", () => {
|
||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel);
|
||||
expect(connectToGitHubBtn).toBeDefined();
|
||||
});
|
||||
@@ -357,7 +353,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||
mockExplorer.notebookManager.gitHubOAuthService.isLoggedIn = jest.fn().mockReturnValue(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const manageGitHubSettingsBtn = buttons.find(
|
||||
(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", () => {
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
|
||||
const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel);
|
||||
expect(connectToGitHubBtn).toBeUndefined();
|
||||
@@ -378,12 +374,13 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
describe("Resource token", () => {
|
||||
const mockCollection = { id: ko.observable("test") } as CollectionBase;
|
||||
useSelectedNode.getState().setSelectedNode(mockCollection);
|
||||
const selectedNodeState = useSelectedNode.getState();
|
||||
beforeAll(() => {
|
||||
mockExplorer = {} as Explorer;
|
||||
mockExplorer.addCollectionText = ko.observable("mockText");
|
||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||
mockExplorer.isResourceTokenCollectionNodeSelected = ko.computed(() => true);
|
||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
||||
mockExplorer.resourceTokenCollection = ko.observable(mockCollection);
|
||||
|
||||
updateUserContext({
|
||||
authType: AuthType.ResourceToken,
|
||||
});
|
||||
@@ -395,7 +392,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
kind: "DocumentDB",
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
expect(buttons.length).toBe(2);
|
||||
expect(buttons[0].commandButtonLabel).toBe("New SQL Query");
|
||||
expect(buttons[0].disabled).toBe(false);
|
||||
|
||||
@@ -21,17 +21,26 @@ import { AuthType } from "../../../AuthType";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import { configContext, Platform } from "../../../ConfigContext";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
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 Explorer from "../../Explorer";
|
||||
import { OpenFullScreen } from "../../OpenFullScreen";
|
||||
import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane";
|
||||
import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane";
|
||||
import { SelectedNodeState } from "../../useSelectedNode";
|
||||
|
||||
let counter = 0;
|
||||
|
||||
export function createStaticCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
|
||||
export function createStaticCommandBarButtons(
|
||||
container: Explorer,
|
||||
selectedNodeState: SelectedNodeState
|
||||
): CommandButtonComponentProps[] {
|
||||
if (userContext.authType === AuthType.ResourceToken) {
|
||||
return createStaticCommandBarButtonsForResourceToken(container);
|
||||
return createStaticCommandBarButtonsForResourceToken(container, selectedNodeState);
|
||||
}
|
||||
|
||||
const newCollectionBtn = createNewCollectionGroup(container);
|
||||
@@ -65,35 +74,36 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
|
||||
buttons.push(createOpenTerminalButton(container));
|
||||
|
||||
buttons.push(createNotebookWorkspaceResetButton(container));
|
||||
if (
|
||||
(userContext.apiType === "Mongo" && container.isShellEnabled() && container.isDatabaseNodeOrNoneSelected()) ||
|
||||
userContext.apiType === "Cassandra"
|
||||
) {
|
||||
buttons.push(createDivider());
|
||||
if (userContext.apiType === "Cassandra") {
|
||||
buttons.push(createOpenCassandraTerminalButton(container));
|
||||
} else {
|
||||
|
||||
buttons.push(createDivider());
|
||||
buttons.push(createOpenPostgreSQLTerminalButton(container));
|
||||
|
||||
if (userContext.apiType === "Mongo" &&
|
||||
container.isShellEnabled() &&
|
||||
selectedNodeState.isDatabaseNodeOrNoneSelected()){
|
||||
buttons.push(createOpenMongoTerminalButton(container));
|
||||
}
|
||||
}
|
||||
if (userContext.apiType === "Cassandra") {
|
||||
buttons.push(createOpenCassandraTerminalButton(container));
|
||||
}
|
||||
} else {
|
||||
if (!container.isRunningOnNationalCloud()) {
|
||||
if (!isRunningOnNationalCloud()) {
|
||||
buttons.push(createEnableNotebooksButton(container));
|
||||
}
|
||||
}
|
||||
|
||||
if (!container.isDatabaseNodeOrNoneSelected()) {
|
||||
if (!selectedNodeState.isDatabaseNodeOrNoneSelected()) {
|
||||
const isQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
|
||||
|
||||
if (isQuerySupported) {
|
||||
buttons.push(createDivider());
|
||||
const newSqlQueryBtn = createNewSQLQueryButton(container);
|
||||
const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState);
|
||||
buttons.push(newSqlQueryBtn);
|
||||
}
|
||||
|
||||
if (isQuerySupported && container.selectedNode() && container.findSelectedCollection()) {
|
||||
if (isQuerySupported && selectedNodeState.findSelectedCollection()) {
|
||||
const openQueryBtn = createOpenQueryButton(container);
|
||||
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton(container)];
|
||||
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton()];
|
||||
buttons.push(openQueryBtn);
|
||||
}
|
||||
|
||||
@@ -103,16 +113,16 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
|
||||
iconSrc: AddStoredProcedureIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
|
||||
},
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
disabled: container.isDatabaseNodeOrNoneSelected(),
|
||||
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
|
||||
};
|
||||
|
||||
newStoredProcedureBtn.children = createScriptCommandButtons(container);
|
||||
newStoredProcedureBtn.children = createScriptCommandButtons(selectedNodeState);
|
||||
buttons.push(newStoredProcedureBtn);
|
||||
}
|
||||
}
|
||||
@@ -120,16 +130,19 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
|
||||
return buttons;
|
||||
}
|
||||
|
||||
export function createContextCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
|
||||
export function createContextCommandBarButtons(
|
||||
container: Explorer,
|
||||
selectedNodeState: SelectedNodeState
|
||||
): 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 newMongoShellBtn: CommandButtonComponentProps = {
|
||||
iconSrc: HostedTerminalIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||
if (container.isShellEnabled()) {
|
||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||
} else {
|
||||
@@ -139,7 +152,7 @@ export function createContextCommandBarButtons(container: Explorer): CommandButt
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
disabled: container.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo",
|
||||
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo",
|
||||
};
|
||||
buttons.push(newMongoShellBtn);
|
||||
}
|
||||
@@ -152,7 +165,7 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
|
||||
{
|
||||
iconSrc: SettingsIcon,
|
||||
iconAlt: "Settings",
|
||||
onCommandClick: () => container.openSettingPane(),
|
||||
onCommandClick: () => useSidePanel.getState().openSidePanel("Settings", <SettingsPane />),
|
||||
commandButtonLabel: undefined,
|
||||
ariaLabel: "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 fullScreenButton: CommandButtonComponentProps = {
|
||||
iconSrc: OpenInTabIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => {
|
||||
container.openSidePanel("Open Full Screen", <OpenFullScreen />);
|
||||
useSidePanel.getState().openSidePanel("Open Full Screen", <OpenFullScreen />);
|
||||
},
|
||||
commandButtonLabel: undefined,
|
||||
ariaLabel: label,
|
||||
tooltipText: label,
|
||||
hasPopup: false,
|
||||
disabled: !container.isHostedDataExplorerEnabled(),
|
||||
disabled: !showOpenFullScreen,
|
||||
className: "OpenFullScreen",
|
||||
};
|
||||
buttons.push(fullScreenButton);
|
||||
@@ -215,7 +231,7 @@ function areScriptsSupported(): boolean {
|
||||
}
|
||||
|
||||
function createNewCollectionGroup(container: Explorer): CommandButtonComponentProps {
|
||||
const label = container.addCollectionText();
|
||||
const label = `New ${getCollectionName()}`;
|
||||
return {
|
||||
iconSrc: AddCollectionIcon,
|
||||
iconAlt: label,
|
||||
@@ -232,7 +248,7 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (container.isServerlessEnabled()) {
|
||||
if (isServerlessAccount()) {
|
||||
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") {
|
||||
const label = "New SQL Query";
|
||||
return {
|
||||
iconSrc: AddSqlQueryIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection);
|
||||
},
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
disabled: container.isDatabaseNodeOrNoneSelected(),
|
||||
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
|
||||
};
|
||||
} else if (userContext.apiType === "Mongo") {
|
||||
const label = "New Query";
|
||||
@@ -292,23 +308,24 @@ function createNewSQLQueryButton(container: Explorer): CommandButtonComponentPro
|
||||
iconSrc: AddSqlQueryIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection);
|
||||
},
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
disabled: container.isDatabaseNodeOrNoneSelected(),
|
||||
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function createScriptCommandButtons(container: Explorer): CommandButtonComponentProps[] {
|
||||
export function createScriptCommandButtons(selectedNodeState: SelectedNodeState): CommandButtonComponentProps[] {
|
||||
const buttons: CommandButtonComponentProps[] = [];
|
||||
|
||||
const shouldEnableScriptsCommands: boolean = !container.isDatabaseNodeOrNoneSelected() && areScriptsSupported();
|
||||
const shouldEnableScriptsCommands: boolean =
|
||||
!selectedNodeState.isDatabaseNodeOrNoneSelected() && areScriptsSupported();
|
||||
|
||||
if (shouldEnableScriptsCommands) {
|
||||
const label = "New Stored Procedure";
|
||||
@@ -316,13 +333,13 @@ export function createScriptCommandButtons(container: Explorer): CommandButtonCo
|
||||
iconSrc: AddStoredProcedureIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
|
||||
},
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
disabled: container.isDatabaseNodeOrNoneSelected(),
|
||||
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
|
||||
};
|
||||
buttons.push(newStoredProcedureBtn);
|
||||
}
|
||||
@@ -333,13 +350,13 @@ export function createScriptCommandButtons(container: Explorer): CommandButtonCo
|
||||
iconSrc: AddUdfIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection);
|
||||
},
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
disabled: container.isDatabaseNodeOrNoneSelected(),
|
||||
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
|
||||
};
|
||||
buttons.push(newUserDefinedFunctionBtn);
|
||||
}
|
||||
@@ -350,13 +367,13 @@ export function createScriptCommandButtons(container: Explorer): CommandButtonCo
|
||||
iconSrc: AddTriggerIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection);
|
||||
},
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
disabled: container.isDatabaseNodeOrNoneSelected(),
|
||||
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
|
||||
};
|
||||
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";
|
||||
return {
|
||||
iconSrc: OpenQueryFromDiskIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => container.openLoadQueryPanel(),
|
||||
onCommandClick: () => useSidePanel.getState().openSidePanel("Load Query", <LoadQueryPane />),
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
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 {
|
||||
const label = "Open Mongo Shell";
|
||||
const tooltip =
|
||||
@@ -529,19 +559,25 @@ function createManageGitHubAccountButton(container: Explorer): CommandButtonComp
|
||||
};
|
||||
}
|
||||
|
||||
function createStaticCommandBarButtonsForResourceToken(container: Explorer): CommandButtonComponentProps[] {
|
||||
const newSqlQueryBtn = createNewSQLQueryButton(container);
|
||||
function createStaticCommandBarButtonsForResourceToken(
|
||||
container: Explorer,
|
||||
selectedNodeState: SelectedNodeState
|
||||
): CommandButtonComponentProps[] {
|
||||
const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState);
|
||||
const openQueryBtn = createOpenQueryButton(container);
|
||||
|
||||
newSqlQueryBtn.disabled = !container.isResourceTokenCollectionNodeSelected();
|
||||
const isResourceTokenCollectionNodeSelected: boolean =
|
||||
container.resourceTokenCollection() &&
|
||||
container.resourceTokenCollection().id() === selectedNodeState.selectedNode?.id();
|
||||
newSqlQueryBtn.disabled = !isResourceTokenCollectionNodeSelected;
|
||||
newSqlQueryBtn.onCommandClick = () => {
|
||||
const resourceTokenCollection: ViewModels.CollectionBase = container.resourceTokenCollection();
|
||||
resourceTokenCollection && resourceTokenCollection.onNewQueryClick(resourceTokenCollection, undefined);
|
||||
};
|
||||
|
||||
openQueryBtn.disabled = !container.isResourceTokenCollectionNodeSelected();
|
||||
openQueryBtn.disabled = !isResourceTokenCollectionNodeSelected;
|
||||
if (!openQueryBtn.disabled) {
|
||||
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton(container)];
|
||||
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton()];
|
||||
}
|
||||
|
||||
return [newSqlQueryBtn, openQueryBtn];
|
||||
|
||||
@@ -14,7 +14,6 @@ import { StyleConstants } from "../../../Common/Constants";
|
||||
import { MemoryUsageInfo } from "../../../Contracts/DataModels";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { ArcadiaMenuPicker } from "../../Controls/Arcadia/ArcadiaMenuPicker";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
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;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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 React from "react";
|
||||
import {
|
||||
ConsoleDataType,
|
||||
NotificationConsoleComponent,
|
||||
NotificationConsoleComponentProps,
|
||||
} from "./NotificationConsoleComponent";
|
||||
import { ConsoleDataType } from "./ConsoleData";
|
||||
import { NotificationConsoleComponent, NotificationConsoleComponentProps } from "./NotificationConsoleComponent";
|
||||
|
||||
describe("NotificationConsoleComponent", () => {
|
||||
const createBlankProps = (): NotificationConsoleComponentProps => {
|
||||
|
||||
@@ -15,26 +15,9 @@ import LoadingIcon from "../../../../images/loading.svg";
|
||||
import ChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
||||
import ChevronUpIcon from "../../../../images/QueryBuilder/CollapseChevronUp_16x.png";
|
||||
import { ClientDefaults, KeyCodes } from "../../../Common/Constants";
|
||||
import { useNotificationConsole } from "../../../hooks/useNotificationConsole";
|
||||
import { userContext } from "../../../UserContext";
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
import { ConsoleData, ConsoleDataType } from "./ConsoleData";
|
||||
|
||||
export interface NotificationConsoleComponentProps {
|
||||
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,
|
||||
makeTransformsRecord,
|
||||
} from "@nteract/core";
|
||||
import { configOption, createConfigCollection, defineConfigOption } from "@nteract/mythic-configuration";
|
||||
import { configOption, defineConfigOption } from "@nteract/mythic-configuration";
|
||||
import { Media } from "@nteract/outputs";
|
||||
import TransformVDOM from "@nteract/transform-vdom";
|
||||
import * as Immutable from "immutable";
|
||||
@@ -242,22 +242,27 @@ export class NotebookClientV2 {
|
||||
);
|
||||
|
||||
// Additional configuration
|
||||
this.store.dispatch(configOption("editorType").action(params.cellEditorType ?? "monaco"));
|
||||
this.store.dispatch(configOption("editorType").action(params.cellEditorType ?? "codemirror"));
|
||||
this.store.dispatch(
|
||||
configOption("autoSaveInterval").action(params.autoSaveInterval ?? Constants.Notebook.autoSaveIntervalMs)
|
||||
);
|
||||
createConfigCollection({
|
||||
key: "monaco",
|
||||
});
|
||||
defineConfigOption({
|
||||
label: "Show Line numbers",
|
||||
key: "monaco.lineNumbers",
|
||||
values: [
|
||||
{ label: "Yes", value: true },
|
||||
{ label: "No", value: false },
|
||||
],
|
||||
defaultValue: true,
|
||||
});
|
||||
this.store.dispatch(configOption("codeMirror.lineNumbers").action(true));
|
||||
|
||||
const readOnlyConfigOption = configOption("codeMirror.readOnly");
|
||||
const readOnlyValue = params.isReadOnly ? "nocursor" : undefined;
|
||||
if (!readOnlyConfigOption) {
|
||||
defineConfigOption({
|
||||
label: "Read-only",
|
||||
key: "codeMirror.readOnly",
|
||||
values: [
|
||||
{ label: "Read-Only", value: "nocursor" },
|
||||
{ label: "Not read-only", value: undefined },
|
||||
],
|
||||
defaultValue: readOnlyValue,
|
||||
});
|
||||
} else {
|
||||
this.store.dispatch(readOnlyConfigOption.action(readOnlyValue));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
.notebookComponentContainer {
|
||||
text-transform:none;
|
||||
line-height:1.28581;
|
||||
letter-spacing:0;
|
||||
font-size:14px;
|
||||
font-weight:400;
|
||||
color:#182026;
|
||||
text-transform: none;
|
||||
line-height: 1.28581;
|
||||
letter-spacing: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #182026;
|
||||
height: 100%;
|
||||
|
||||
.hotKeys {
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
KernelRef,
|
||||
RemoteKernelProps,
|
||||
selectors,
|
||||
ServerConfig as JupyterServerConfig,
|
||||
ServerConfig as JupyterServerConfig
|
||||
} from "@nteract/core";
|
||||
import { Channels, childOf, createMessage, JupyterMessage, message, ofMessageType } from "@nteract/messaging";
|
||||
import { RecordOf } from "immutable";
|
||||
@@ -29,14 +29,13 @@ import {
|
||||
switchMap,
|
||||
take,
|
||||
tap,
|
||||
timeout,
|
||||
timeout
|
||||
} from "rxjs/operators";
|
||||
import { webSocket } from "rxjs/webSocket";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import { Areas } from "../../../Common/Constants";
|
||||
import { Action as TelemetryAction, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { decryptJWTToken } from "../../../Utils/AuthorizationUtils";
|
||||
import { logConsoleError, logConsoleInfo } from "../../../Utils/NotificationConsoleUtils";
|
||||
import * as FileSystemUtil from "../FileSystemUtil";
|
||||
import * as cdbActions from "../NotebookComponent/actions";
|
||||
@@ -105,15 +104,10 @@ const formWebSocketURL = (serverConfig: NotebookServiceConfig, kernelId: string,
|
||||
params.append("session_id", sessionId);
|
||||
}
|
||||
|
||||
const userId = getUserPuid();
|
||||
if (userId) {
|
||||
params.append("user_id", userId);
|
||||
}
|
||||
|
||||
const q = params.toString();
|
||||
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");
|
||||
};
|
||||
@@ -289,7 +283,6 @@ export const launchWebSocketKernelEpic = (
|
||||
return EMPTY;
|
||||
}
|
||||
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
|
||||
serverConfig.userPuid = getUserPuid();
|
||||
|
||||
const {
|
||||
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
|
||||
* @param action$
|
||||
|
||||
@@ -53,7 +53,7 @@ export class NotebookContainerClient {
|
||||
|
||||
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
||||
try {
|
||||
const response = await fetch(`${notebookServerEndpoint}/api/metrics/memory`, {
|
||||
const response = await fetch(`${notebookServerEndpoint}api/metrics/memory`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: authToken,
|
||||
@@ -130,19 +130,23 @@ export class NotebookContainerClient {
|
||||
}
|
||||
|
||||
private async recreateNotebookWorkspaceAsync(): Promise<void> {
|
||||
const explorer = window.dataExplorer;
|
||||
const { databaseAccount } = userContext;
|
||||
if (!databaseAccount?.id) {
|
||||
throw new Error("DataExplorer not initialized");
|
||||
}
|
||||
|
||||
const notebookWorkspaceManager = explorer.notebookWorkspaceManager;
|
||||
/*
|
||||
try {
|
||||
await notebookWorkspaceManager.deleteNotebookWorkspaceAsync(databaseAccount?.id, "default");
|
||||
await notebookWorkspaceManager.createNotebookWorkspaceAsync(databaseAccount?.id, "default");
|
||||
await destroy(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, "default");
|
||||
await createOrUpdate(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
"default"
|
||||
);
|
||||
} catch (error) {
|
||||
Logger.logError(getErrorMessage(error), "NotebookContainerClient/recreateNotebookWorkspaceAsync");
|
||||
return Promise.reject(error);
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export class NotebookContentClient {
|
||||
private notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>,
|
||||
public notebookBasePath: ko.Observable<string>,
|
||||
private contentProvider: IContentProvider
|
||||
) {}
|
||||
) { }
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -35,6 +38,7 @@ export class NotebookContentClient {
|
||||
}
|
||||
|
||||
const type = "notebook";
|
||||
|
||||
return this.contentProvider
|
||||
.create<"notebook">(this.getServerConfig(), parent.path, { type })
|
||||
.toPromise()
|
||||
|
||||
@@ -14,13 +14,13 @@ import { MemoryUsageInfo } from "../../Contracts/DataModels";
|
||||
import { GitHubClient } from "../../GitHub/GitHubClient";
|
||||
import { GitHubContentProvider } from "../../GitHub/GitHubContentProvider";
|
||||
import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService";
|
||||
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||
import { JunoClient } from "../../Juno/JunoClient";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { getFullName } from "../../Utils/UserUtils";
|
||||
import Explorer from "../Explorer";
|
||||
import { ContextualPaneBase } from "../Panes/ContextualPaneBase";
|
||||
import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane";
|
||||
import { PublishNotebookPane } from "../Panes/PublishNotebookPane/PublishNotebookPane";
|
||||
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
|
||||
@@ -56,8 +56,6 @@ export default class NotebookManager {
|
||||
public gitHubOAuthService: GitHubOAuthService;
|
||||
public gitHubClient: GitHubClient;
|
||||
|
||||
public gitHubReposPane: ContextualPaneBase;
|
||||
|
||||
public initialize(params: NotebookManagerOptions): void {
|
||||
this.params = params;
|
||||
this.junoClient = new JunoClient();
|
||||
@@ -98,7 +96,7 @@ export default class NotebookManager {
|
||||
this.gitHubOAuthService.getTokenObservable().subscribe((token) => {
|
||||
this.gitHubClient.setToken(token?.access_token);
|
||||
if (this?.gitHubOAuthService.isLoggedIn()) {
|
||||
this.params.container.closeSidePanel();
|
||||
useSidePanel.getState().closeSidePanel();
|
||||
this.params.container.openGitHubReposPanel("Manager GitHub settings", this.junoClient);
|
||||
}
|
||||
|
||||
@@ -127,37 +125,37 @@ export default class NotebookManager {
|
||||
onTakeSnapshot: (request: SnapshotRequest) => void,
|
||||
onClosePanel: () => void
|
||||
): Promise<void> {
|
||||
const explorer = this.params.container;
|
||||
explorer.openSidePanel(
|
||||
"Publish Notebook",
|
||||
<PublishNotebookPane
|
||||
explorer={this.params.container}
|
||||
junoClient={this.junoClient}
|
||||
closePanel={this.params.container.closeSidePanel}
|
||||
openNotificationConsole={this.params.container.expandConsole}
|
||||
name={name}
|
||||
author={getFullName()}
|
||||
notebookContent={content}
|
||||
notebookContentRef={notebookContentRef}
|
||||
onTakeSnapshot={onTakeSnapshot}
|
||||
/>,
|
||||
onClosePanel
|
||||
);
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"Publish Notebook",
|
||||
<PublishNotebookPane
|
||||
explorer={this.params.container}
|
||||
junoClient={this.junoClient}
|
||||
name={name}
|
||||
author={getFullName()}
|
||||
notebookContent={content}
|
||||
notebookContentRef={notebookContentRef}
|
||||
onTakeSnapshot={onTakeSnapshot}
|
||||
/>,
|
||||
onClosePanel
|
||||
);
|
||||
}
|
||||
|
||||
public openCopyNotebookPane(name: string, content: string): void {
|
||||
const { container } = this.params;
|
||||
container.openSidePanel(
|
||||
"Copy Notebook",
|
||||
<CopyNotebookPane
|
||||
container={container}
|
||||
closePanel={container.closeSidePanel}
|
||||
junoClient={this.junoClient}
|
||||
gitHubOAuthService={this.gitHubOAuthService}
|
||||
name={name}
|
||||
content={content}
|
||||
/>
|
||||
);
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"Copy Notebook",
|
||||
<CopyNotebookPane
|
||||
container={container}
|
||||
junoClient={this.junoClient}
|
||||
gitHubOAuthService={this.gitHubOAuthService}
|
||||
name={name}
|
||||
content={content}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Octokit's error handler uses any
|
||||
|
||||
@@ -1,56 +1,68 @@
|
||||
.NotebookReadOnlyRender {
|
||||
.nteract-cell-container {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.nteract-cell-container {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.nteract-cell {
|
||||
padding: 0.5px;
|
||||
border: 1px solid #ffffff;
|
||||
border-left: 3px solid #ffffff;
|
||||
}
|
||||
.nteract-cell {
|
||||
padding: 0.5px;
|
||||
border: 1px solid #ffffff;
|
||||
border-left: 3px solid #ffffff;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.CodeMirror-scroll {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.CodeMirror-lines {
|
||||
cursor: default;
|
||||
}
|
||||
.CodeMirror-lines {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.nteract-cell:hover {
|
||||
border: 1px solid #0078d4;
|
||||
border-left: 3px solid #0078d4;
|
||||
.CodeMirror {
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.CodeMirror-scroll,
|
||||
.CodeMirror-linenumber,
|
||||
.CodeMirror-gutters {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.nteract-cell-outputs {
|
||||
border-top: 1px solid #d7d7d7;
|
||||
}
|
||||
.nteract-cell:hover {
|
||||
border: 1px solid #0078d4;
|
||||
border-left: 3px solid #0078d4;
|
||||
|
||||
.nteract-md-cell {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.CodeMirror-scroll,
|
||||
.CodeMirror-linenumber,
|
||||
.CodeMirror-gutters {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.nteract-cell-outputs {
|
||||
padding: 10px;
|
||||
border-top: 1px solid #ffffff;
|
||||
|
||||
pre {
|
||||
background-color: #ffffff;
|
||||
border: none;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
border-top: 1px solid #d7d7d7;
|
||||
}
|
||||
|
||||
.nteract-md-cell {
|
||||
background-color: #f5f5f5;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.nteract-cell:hover.nteract-md-cell {
|
||||
background-color: #ffffff;
|
||||
.nteract-cell-outputs {
|
||||
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 { 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 Prompt, { PassedPromptProps } from "@nteract/stateful-components/lib/inputs/prompt";
|
||||
import * as React from "react";
|
||||
@@ -67,8 +67,8 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
||||
? () => <SandboxOutputs id={id} contentRef={contentRef} />
|
||||
: undefined,
|
||||
editor: {
|
||||
monaco: (props: PassedEditorProps) =>
|
||||
this.props.hideInputs ? <></> : <MonacoEditor readOnly={true} {...props} editorType={"monaco"} />,
|
||||
codemirror: (props: PassedEditorProps) =>
|
||||
this.props.hideInputs ? <></> : <CodeMirrorEditor {...props} editorType="codemirror" />,
|
||||
},
|
||||
}}
|
||||
</CodeCell>
|
||||
@@ -84,8 +84,8 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
||||
<RawCell id={id} contentRef={contentRef} cell_type="raw">
|
||||
{{
|
||||
editor: {
|
||||
monaco: (props: PassedEditorProps) =>
|
||||
this.props.hideInputs ? <></> : <MonacoEditor {...props} readOnly={true} editorType={"monaco"} />,
|
||||
codemirror: (props: PassedEditorProps) =>
|
||||
this.props.hideInputs ? <></> : <CodeMirrorEditor {...props} editorType="codemirror" />,
|
||||
},
|
||||
}}
|
||||
</RawCell>
|
||||
|
||||
@@ -3,110 +3,122 @@
|
||||
@HighlightColor: #0078d4;
|
||||
|
||||
.NotebookRendererContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.NotebookRenderer {
|
||||
overflow: auto;
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
flex-grow: 1;
|
||||
|
||||
.nteract-cells {
|
||||
padding-top: 0px;
|
||||
.nteract-cells {
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror-scroll, .CodeMirror-linenumber, .CodeMirror-gutters {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.nteract-cell:hover {
|
||||
border: 1px solid @HoverColor;
|
||||
border-left: 3px solid @HoverColor;
|
||||
|
||||
.CellContextMenuButton {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.CodeMirror-scroll {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.nteract-cell-container.selected {
|
||||
.nteract-cell {
|
||||
border: 1px solid @HighlightColor;
|
||||
border-left: 3px solid @HighlightColor;
|
||||
}
|
||||
.CodeMirror-scroll,
|
||||
.CodeMirror-linenumber,
|
||||
.CodeMirror-gutters {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.CodeMirror-linenumber {
|
||||
color: #015CDA;
|
||||
}
|
||||
.nteract-cell:hover {
|
||||
border: 1px solid @HoverColor;
|
||||
border-left: 3px solid @HoverColor;
|
||||
|
||||
.nteract-cell-outputs {
|
||||
border-top: 1px solid @HoverColor;
|
||||
}
|
||||
.CellContextMenuButton {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nteract-md-cell {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.nteract-cell-container.selected {
|
||||
.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 {
|
||||
padding: 10px;
|
||||
border-top: 1px solid #ffffff;
|
||||
|
||||
pre {
|
||||
background-color: #ffffff;
|
||||
border: none;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
border-top: 1px solid @HoverColor;
|
||||
}
|
||||
|
||||
.nteract-md-cell {
|
||||
background-color: #f5f5f5;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.nteract-cell:hover.nteract-md-cell {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.nteract-cell-outputs {
|
||||
padding: 10px;
|
||||
border-top: 1px solid #ffffff;
|
||||
|
||||
.nteract-md-cell .ntreact-cell-source {
|
||||
width: 100%;
|
||||
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;
|
||||
}
|
||||
|
||||
.nteract-md-cell .ntreact-cell-source {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Undo tree.less
|
||||
.expanded::before {
|
||||
content: '';
|
||||
content: "";
|
||||
}
|
||||
|
||||
.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 { actions, ContentRef, selectors } from "@nteract/core";
|
||||
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 * as React from "react";
|
||||
import { DndProvider } from "react-dnd";
|
||||
@@ -120,7 +120,9 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
|
||||
<CodeCell id={id} contentRef={contentRef} cell_type="code">
|
||||
{{
|
||||
editor: {
|
||||
monaco: (props: PassedEditorProps) => <MonacoEditor {...props} editorType={"monaco"} />,
|
||||
codemirror: (props: PassedEditorProps) => (
|
||||
<CodeMirrorEditor {...props} editorType="codemirror" />
|
||||
),
|
||||
},
|
||||
prompt: ({ id, contentRef }: { id: CellId; contentRef: ContentRef }) => (
|
||||
<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">
|
||||
{{
|
||||
editor: {
|
||||
monaco: (props: PassedEditorProps) => <MonacoEditor {...props} editorType={"monaco"} />,
|
||||
codemirror: (props: PassedEditorProps) => (
|
||||
<CodeMirrorEditor {...props} editorType="codemirror" />
|
||||
),
|
||||
},
|
||||
toolbar: () => <CellToolbar id={id} contentRef={contentRef} />,
|
||||
}}
|
||||
@@ -157,7 +161,9 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
|
||||
<RawCell id={id} contentRef={contentRef} cell_type="raw">
|
||||
{{
|
||||
editor: {
|
||||
monaco: (props: PassedEditorProps) => <MonacoEditor {...props} editorType={"monaco"} />,
|
||||
codemirror: (props: PassedEditorProps) => (
|
||||
<CodeMirrorEditor {...props} editorType="codemirror" />
|
||||
),
|
||||
},
|
||||
toolbar: () => <CellToolbar id={id} contentRef={contentRef} />,
|
||||
}}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
/* eslint jsx-a11y/no-static-element-interactions: 0 */
|
||||
/* eslint jsx-a11y/click-events-have-key-events: 0 */
|
||||
|
||||
import { actions, AppState, ContentRef, selectors } from "@nteract/core";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Dispatch } from "redux";
|
||||
|
||||
import { actions, selectors, ContentRef, AppState } from "@nteract/core";
|
||||
|
||||
interface ComponentProps {
|
||||
id: string;
|
||||
contentRef: ContentRef;
|
||||
@@ -70,7 +69,7 @@ export class HijackScroll extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
const makeMapStateToProps = (initialState: AppState, ownProps: ComponentProps) => {
|
||||
const makeMapStateToProps = (_initialState: AppState, ownProps: ComponentProps) => {
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
const { id, contentRef } = ownProps;
|
||||
const model = selectors.model(state, { contentRef });
|
||||
@@ -87,7 +86,7 @@ const makeMapStateToProps = (initialState: AppState, ownProps: ComponentProps) =
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
const makeMapDispatchToProps = (initialDispatch: Dispatch, ownProps: ComponentProps) => {
|
||||
const makeMapDispatchToProps = (_initialDispatch: Dispatch, ownProps: ComponentProps) => {
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
selectCell: () => dispatch(actions.focusCell({ id: ownProps.id, contentRef: ownProps.contentRef })),
|
||||
});
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { CellId } from "@nteract/commutable";
|
||||
import { actions, AppState, ContentRef, selectors } from "@nteract/core";
|
||||
import Immutable from "immutable";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Dispatch } from "redux";
|
||||
|
||||
import { CellId } from "@nteract/commutable";
|
||||
import { actions, AppState, ContentRef, selectors } from "@nteract/core";
|
||||
|
||||
interface ComponentProps {
|
||||
contentRef: ContentRef;
|
||||
children: React.ReactNode;
|
||||
@@ -107,7 +106,7 @@ export class KeyboardShortcuts extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export const makeMapStateToProps = (state: AppState, ownProps: ComponentProps) => {
|
||||
export const makeMapStateToProps = (_state: AppState, ownProps: ComponentProps) => {
|
||||
const { contentRef } = ownProps;
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
const model = selectors.model(state, { contentRef });
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
jest.mock("./NotebookComponent/store");
|
||||
jest.mock("@nteract/core");
|
||||
import { defineConfigOption } from "@nteract/mythic-configuration";
|
||||
import { NotebookClientV2 } from "./NotebookClientV2";
|
||||
import configureStore from "./NotebookComponent/store";
|
||||
import { defineConfigOption } from "@nteract/mythic-configuration";
|
||||
|
||||
describe("auto start kernel", () => {
|
||||
it("configure autoStartKernelOnNotebookOpen properly depending whether notebook is/is not read-only", async () => {
|
||||
@@ -24,6 +24,12 @@ describe("auto start kernel", () => {
|
||||
defaultValue: 1234,
|
||||
});
|
||||
|
||||
defineConfigOption({
|
||||
label: "Line numbers",
|
||||
key: "codeMirror.lineNumbers",
|
||||
defaultValue: true,
|
||||
});
|
||||
|
||||
[true, false].forEach((isReadOnly) => {
|
||||
new NotebookClientV2({
|
||||
connectionInfo: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as ko from "knockout";
|
||||
import { ActionContracts } from "../Contracts/ExplorerContracts";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import Explorer from "./Explorer";
|
||||
import { ActionContracts } from "../../Contracts/ExplorerContracts";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import Explorer from "../Explorer";
|
||||
import { handleOpenAction } from "./OpenActions";
|
||||
|
||||
describe("OpenActions", () => {
|
||||
@@ -9,7 +9,6 @@ describe("OpenActions", () => {
|
||||
let explorer: Explorer;
|
||||
let database: ViewModels.Database;
|
||||
let collection: ViewModels.Collection;
|
||||
let databases: ViewModels.Database[];
|
||||
|
||||
beforeEach(() => {
|
||||
explorer = {} as Explorer;
|
||||
@@ -19,7 +18,6 @@ describe("OpenActions", () => {
|
||||
id: ko.observable("db"),
|
||||
collections: ko.observableArray<ViewModels.Collection>([]),
|
||||
} as ViewModels.Database;
|
||||
databases = [database];
|
||||
collection = {
|
||||
id: ko.observable("coll"),
|
||||
} as ViewModels.Collection;
|
||||
@@ -68,7 +66,7 @@ describe("OpenActions", () => {
|
||||
paneKind: "AddCollection",
|
||||
};
|
||||
|
||||
const actionHandled = handleOpenAction(action, [], explorer);
|
||||
handleOpenAction(action, [], explorer);
|
||||
expect(explorer.onNewCollectionClicked).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -78,7 +76,7 @@ describe("OpenActions", () => {
|
||||
paneKind: ActionContracts.PaneKind.AddCollection,
|
||||
};
|
||||
|
||||
const actionHandled = handleOpenAction(action, [], explorer);
|
||||
handleOpenAction(action, [], explorer);
|
||||
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.
|
||||
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";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import Explorer from "./Explorer";
|
||||
|
||||
export function handleOpenAction(
|
||||
action: ActionContracts.DataExplorerAction,
|
||||
databases: ViewModels.Database[],
|
||||
explorer: Explorer
|
||||
): boolean {
|
||||
if (
|
||||
action.actionType === ActionContracts.ActionType.OpenCollectionTab ||
|
||||
(<any>action).actionType === ActionContracts.ActionType[ActionContracts.ActionType.OpenCollectionTab]
|
||||
) {
|
||||
openCollectionTab(<ActionContracts.OpenCollectionTab>action, databases);
|
||||
return true;
|
||||
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++) {
|
||||
const 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;
|
||||
}
|
||||
|
||||
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;
|
||||
return "SELECT * FROM c";
|
||||
}
|
||||
|
||||
function openCollectionTab(
|
||||
@@ -65,7 +64,7 @@ function openCollectionTab(
|
||||
|
||||
if (
|
||||
action.tabKind === ActionContracts.TabKind.SQLDocuments ||
|
||||
(<any>action).tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLDocuments]
|
||||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLDocuments]
|
||||
) {
|
||||
collection.onDocumentDBDocumentsClick();
|
||||
break;
|
||||
@@ -73,7 +72,7 @@ function openCollectionTab(
|
||||
|
||||
if (
|
||||
action.tabKind === ActionContracts.TabKind.MongoDocuments ||
|
||||
(<any>action).tabKind === ActionContracts.TabKind[ActionContracts.TabKind.MongoDocuments]
|
||||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.MongoDocuments]
|
||||
) {
|
||||
collection.onMongoDBDocumentsClick();
|
||||
break;
|
||||
@@ -81,7 +80,7 @@ function openCollectionTab(
|
||||
|
||||
if (
|
||||
action.tabKind === ActionContracts.TabKind.SchemaAnalyzer ||
|
||||
(<any>action).tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SchemaAnalyzer]
|
||||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SchemaAnalyzer]
|
||||
) {
|
||||
collection.onSchemaAnalyzerClick();
|
||||
break;
|
||||
@@ -89,7 +88,7 @@ function openCollectionTab(
|
||||
|
||||
if (
|
||||
action.tabKind === ActionContracts.TabKind.TableEntities ||
|
||||
(<any>action).tabKind === ActionContracts.TabKind[ActionContracts.TabKind.TableEntities]
|
||||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.TableEntities]
|
||||
) {
|
||||
collection.onTableEntitiesClick();
|
||||
break;
|
||||
@@ -97,7 +96,7 @@ function openCollectionTab(
|
||||
|
||||
if (
|
||||
action.tabKind === ActionContracts.TabKind.Graph ||
|
||||
(<any>action).tabKind === ActionContracts.TabKind[ActionContracts.TabKind.Graph]
|
||||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.Graph]
|
||||
) {
|
||||
collection.onGraphDocumentsClick();
|
||||
break;
|
||||
@@ -105,19 +104,19 @@ function openCollectionTab(
|
||||
|
||||
if (
|
||||
action.tabKind === ActionContracts.TabKind.SQLQuery ||
|
||||
(<any>action).tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLQuery]
|
||||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLQuery]
|
||||
) {
|
||||
collection.onNewQueryClick(
|
||||
collection,
|
||||
null,
|
||||
generateQueryText(<ActionContracts.OpenQueryTab>action, collection.partitionKeyProperty)
|
||||
undefined,
|
||||
generateQueryText(action as ActionContracts.OpenQueryTab, collection.partitionKeyProperty)
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if (
|
||||
action.tabKind === ActionContracts.TabKind.ScaleSettings ||
|
||||
(<any>action).tabKind === ActionContracts.TabKind[ActionContracts.TabKind.ScaleSettings]
|
||||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.ScaleSettings]
|
||||
) {
|
||||
collection.onSettingsClick();
|
||||
break;
|
||||
@@ -138,49 +137,59 @@ function openCollectionTab(
|
||||
function openPane(action: ActionContracts.OpenPane, explorer: Explorer) {
|
||||
if (
|
||||
action.paneKind === ActionContracts.PaneKind.AddCollection ||
|
||||
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.AddCollection]
|
||||
action.paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.AddCollection]
|
||||
) {
|
||||
explorer.onNewCollectionClicked();
|
||||
} else if (
|
||||
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 (
|
||||
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) {
|
||||
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";
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user