Compare commits
1 Commits
fabricbot-
...
eslint/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ff5efcea4 |
@@ -81,9 +81,17 @@ src/Explorer/Tables/DataTable/DataTableBindingManager.ts
|
|||||||
src/Explorer/Tables/DataTable/DataTableBuilder.ts
|
src/Explorer/Tables/DataTable/DataTableBuilder.ts
|
||||||
src/Explorer/Tables/DataTable/DataTableContextMenu.ts
|
src/Explorer/Tables/DataTable/DataTableContextMenu.ts
|
||||||
src/Explorer/Tables/DataTable/DataTableOperationManager.ts
|
src/Explorer/Tables/DataTable/DataTableOperationManager.ts
|
||||||
|
src/Explorer/Tables/DataTable/DataTableOperations.ts
|
||||||
src/Explorer/Tables/DataTable/DataTableViewModel.ts
|
src/Explorer/Tables/DataTable/DataTableViewModel.ts
|
||||||
|
src/Explorer/Tables/DataTable/TableCommands.ts
|
||||||
|
src/Explorer/Tables/DataTable/TableEntityCache.ts
|
||||||
src/Explorer/Tables/DataTable/TableEntityListViewModel.ts
|
src/Explorer/Tables/DataTable/TableEntityListViewModel.ts
|
||||||
|
src/Explorer/Tables/Entities.ts
|
||||||
|
src/Explorer/Tables/QueryBuilder/ClauseGroup.ts
|
||||||
|
src/Explorer/Tables/QueryBuilder/ClauseGroupViewModel.ts
|
||||||
src/Explorer/Tables/QueryBuilder/CustomTimestampHelper.ts
|
src/Explorer/Tables/QueryBuilder/CustomTimestampHelper.ts
|
||||||
|
src/Explorer/Tables/QueryBuilder/QueryBuilderViewModel.ts
|
||||||
|
src/Explorer/Tables/QueryBuilder/QueryClauseViewModel.ts
|
||||||
src/Explorer/Tables/TableDataClient.ts
|
src/Explorer/Tables/TableDataClient.ts
|
||||||
src/Explorer/Tables/TableEntityProcessor.ts
|
src/Explorer/Tables/TableEntityProcessor.ts
|
||||||
src/Explorer/Tables/Utilities.ts
|
src/Explorer/Tables/Utilities.ts
|
||||||
@@ -98,7 +106,6 @@ src/Explorer/Tabs/ScriptTabBase.ts
|
|||||||
src/Explorer/Tabs/TabComponents.ts
|
src/Explorer/Tabs/TabComponents.ts
|
||||||
src/Explorer/Tabs/TabsBase.ts
|
src/Explorer/Tabs/TabsBase.ts
|
||||||
src/Explorer/Tabs/TriggerTab.ts
|
src/Explorer/Tabs/TriggerTab.ts
|
||||||
src/Explorer/Tabs/UserDefinedFunctionTab.ts
|
|
||||||
src/Explorer/Tree/AccessibleVerticalList.ts
|
src/Explorer/Tree/AccessibleVerticalList.ts
|
||||||
src/Explorer/Tree/Collection.ts
|
src/Explorer/Tree/Collection.ts
|
||||||
src/Explorer/Tree/ConflictId.ts
|
src/Explorer/Tree/ConflictId.ts
|
||||||
@@ -107,12 +114,15 @@ src/Explorer/Tree/ObjectId.ts
|
|||||||
src/Explorer/Tree/ResourceTokenCollection.ts
|
src/Explorer/Tree/ResourceTokenCollection.ts
|
||||||
src/Explorer/Tree/StoredProcedure.ts
|
src/Explorer/Tree/StoredProcedure.ts
|
||||||
src/Explorer/Tree/TreeComponents.ts
|
src/Explorer/Tree/TreeComponents.ts
|
||||||
|
src/Explorer/Tree/Trigger.ts
|
||||||
src/Explorer/WaitsForTemplateViewModel.ts
|
src/Explorer/WaitsForTemplateViewModel.ts
|
||||||
src/GitHub/GitHubClient.test.ts
|
src/GitHub/GitHubClient.test.ts
|
||||||
src/GitHub/GitHubClient.ts
|
src/GitHub/GitHubConnector.ts
|
||||||
|
src/GitHub/GitHubOAuthService.ts
|
||||||
src/Index.ts
|
src/Index.ts
|
||||||
|
src/Juno/JunoClient.test.ts
|
||||||
|
src/Juno/JunoClient.ts
|
||||||
src/Platform/Hosted/Authorization.ts
|
src/Platform/Hosted/Authorization.ts
|
||||||
src/ReactDevTools.ts
|
|
||||||
src/Shared/Constants.ts
|
src/Shared/Constants.ts
|
||||||
src/Shared/DefaultExperienceUtility.test.ts
|
src/Shared/DefaultExperienceUtility.test.ts
|
||||||
src/Shared/DefaultExperienceUtility.ts
|
src/Shared/DefaultExperienceUtility.ts
|
||||||
@@ -130,14 +140,20 @@ src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
|
|||||||
src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
|
src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
|
||||||
src/Explorer/Controls/TreeComponent/TreeComponent.tsx
|
src/Explorer/Controls/TreeComponent/TreeComponent.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
|
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
|
||||||
|
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
|
||||||
|
src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx
|
||||||
|
src/Explorer/Graph/GraphExplorerComponent/LeftPaneComponent.tsx
|
||||||
|
src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx
|
||||||
|
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.test.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.tsx
|
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx
|
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
|
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
|
||||||
src/Explorer/Menus/CommandBar/CommandBarUtil.tsx
|
src/Explorer/Menus/CommandBar/CommandBarUtil.tsx
|
||||||
src/Explorer/Notebook/NotebookComponent/NotebookComponentAdapter.tsx
|
src/Explorer/Notebook/NotebookComponent/NotebookComponentAdapter.tsx
|
||||||
|
src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx
|
||||||
src/Explorer/Notebook/NotebookComponent/VirtualCommandBarComponent.tsx
|
src/Explorer/Notebook/NotebookComponent/VirtualCommandBarComponent.tsx
|
||||||
src/Explorer/Notebook/NotebookComponent/contents/index.tsx
|
src/Explorer/Notebook/NotebookComponent/contents/index.tsx
|
||||||
src/Explorer/Notebook/NotebookRenderer/NotebookRenderer.tsx
|
src/Explorer/Notebook/NotebookRenderer/NotebookReadOnlyRenderer.tsx
|
||||||
src/Explorer/Notebook/NotebookRenderer/decorators/draggable/index.tsx
|
src/Explorer/Notebook/NotebookRenderer/decorators/draggable/index.tsx
|
||||||
src/Explorer/Notebook/NotebookRenderer/decorators/hijack-scroll/index.tsx
|
src/Explorer/Notebook/NotebookRenderer/decorators/hijack-scroll/index.tsx
|
||||||
src/Explorer/Notebook/NotebookRenderer/decorators/kbd-shortcuts/index.tsx
|
src/Explorer/Notebook/NotebookRenderer/decorators/kbd-shortcuts/index.tsx
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ module.exports = {
|
|||||||
"@typescript-eslint/switch-exhaustiveness-check": "error",
|
"@typescript-eslint/switch-exhaustiveness-check": "error",
|
||||||
"@typescript-eslint/no-unused-vars": "error",
|
"@typescript-eslint/no-unused-vars": "error",
|
||||||
"@typescript-eslint/no-extraneous-class": "error",
|
"@typescript-eslint/no-extraneous-class": "error",
|
||||||
|
"no-null/no-null": "error",
|
||||||
"@typescript-eslint/no-explicit-any": "error",
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
|
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
|
||||||
eqeqeq: "error",
|
eqeqeq: "error",
|
||||||
|
|||||||
30
.github/fabricbot.json
vendored
@@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "1.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"taskType": "trigger",
|
|
||||||
"capabilityId": "AutoMerge",
|
|
||||||
"subCapability": "AutoMerge",
|
|
||||||
"version": "1.0",
|
|
||||||
"id": "LUEPwPETV",
|
|
||||||
"config": {
|
|
||||||
"taskName": "Auto Merge",
|
|
||||||
"label": "automerge",
|
|
||||||
"minMinutesOpen": "5",
|
|
||||||
"mergeType": "squash",
|
|
||||||
"deleteBranches": true,
|
|
||||||
"requireAllStatuses": true,
|
|
||||||
"requireSpecificCheckRuns": false,
|
|
||||||
"usePrDescriptionAsCommitMessage": true,
|
|
||||||
"requireAllStatuses_exemptList": [
|
|
||||||
"Azure Pipelines",
|
|
||||||
"Dependabot",
|
|
||||||
"GitHub Pages",
|
|
||||||
"Check Enforcer"
|
|
||||||
],
|
|
||||||
"silentMode": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"userGroups": []
|
|
||||||
}
|
|
||||||
4
.github/workflows/ci.yml
vendored
@@ -92,11 +92,11 @@ jobs:
|
|||||||
name: dist
|
name: dist
|
||||||
path: dist/
|
path: dist/
|
||||||
- name: Upload build to preview blob storage
|
- name: Upload build to preview blob storage
|
||||||
run: az storage blob upload-batch -d '$web' -s 'dist' --account-name cosmosexplorerpreview --destination-path "${{github.event.pull_request.head.sha || github.sha}}" --account-key="${PREVIEW_STORAGE_KEY}"
|
run: az storage blob upload-batch -d '$web' -s 'dist' --account-name cosmosexplorerpreview --subscription cosmosdb-portalteam-generaldemo --destination-path "${{github.event.pull_request.head.sha || github.sha}}" --account-key="${PREVIEW_STORAGE_KEY}"
|
||||||
env:
|
env:
|
||||||
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
||||||
- name: Upload preview config to blob storage
|
- name: Upload preview config to blob storage
|
||||||
run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}"
|
run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --subscription cosmosdb-portalteam-generaldemo --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}"
|
||||||
env:
|
env:
|
||||||
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
||||||
endtoendemulator:
|
endtoendemulator:
|
||||||
|
|||||||
6
.vscode/launch.json
vendored
@@ -12,8 +12,7 @@
|
|||||||
"--inspect-brk",
|
"--inspect-brk",
|
||||||
"${workspaceRoot}/node_modules/jest/bin/jest.js",
|
"${workspaceRoot}/node_modules/jest/bin/jest.js",
|
||||||
"--runInBand",
|
"--runInBand",
|
||||||
"--coverage",
|
"--coverage", "false"
|
||||||
"false"
|
|
||||||
],
|
],
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"internalConsoleOptions": "neverOpen",
|
"internalConsoleOptions": "neverOpen",
|
||||||
@@ -27,8 +26,7 @@
|
|||||||
"--inspect-brk",
|
"--inspect-brk",
|
||||||
"${workspaceRoot}/node_modules/jest/bin/jest.js",
|
"${workspaceRoot}/node_modules/jest/bin/jest.js",
|
||||||
"${fileBasenameNoExtension}",
|
"${fileBasenameNoExtension}",
|
||||||
"--coverage",
|
"--coverage", "false",
|
||||||
"false",
|
|
||||||
// "--watch",
|
// "--watch",
|
||||||
// // --no-cache only used to make --watch work. Otherwise jest ignores the breakpoints.
|
// // --no-cache only used to make --watch work. Otherwise jest ignores the breakpoints.
|
||||||
// // https://github.com/facebook/jest/issues/6683
|
// // https://github.com/facebook/jest/issues/6683
|
||||||
|
|||||||
3
.vscode/settings.json
vendored
@@ -22,6 +22,5 @@
|
|||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": true,
|
"source.fixAll.eslint": true,
|
||||||
"source.organizeImports": true
|
"source.organizeImports": true
|
||||||
},
|
}
|
||||||
"typescript.preferences.importModuleSpecifier": "non-relative"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
{
|
{
|
||||||
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com",
|
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com"
|
||||||
"isTerminalEnabled" : true,
|
|
||||||
"isPhoenixEnabled" : true
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
{
|
{
|
||||||
"JUNO_ENDPOINT": "https://tools.cosmos.azure.com",
|
"JUNO_ENDPOINT": "https://tools.cosmos.azure.com"
|
||||||
"isTerminalEnabled" : false,
|
|
||||||
"isPhoenixEnabled" : false
|
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 18 KiB |
@@ -1,23 +0,0 @@
|
|||||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g clip-path="url(#clip0_480_185430)">
|
|
||||||
<path d="M35.4911 6.39638L33.4106 4.31591L37.559 0.167553C37.6611 0.0654497 37.7996 0.00808785 37.944 0.00808785C38.0884 0.00808801 38.2269 0.0654482 38.329 0.167551L39.641 1.47963C39.7431 1.58173 39.8005 1.72021 39.8005 1.86461C39.8005 2.009 39.7431 2.14749 39.641 2.24959L35.4927 6.39795L35.4911 6.39638Z" fill="#32BEDD"/>
|
|
||||||
<path d="M4.45313 33.6455L6.53988 35.7323L2.44494 39.8272C2.34367 39.9285 2.20632 39.9853 2.06311 39.9853C1.91989 39.9853 1.78254 39.9285 1.68127 39.8272L0.364478 38.5104C0.312974 38.4606 0.271905 38.401 0.243665 38.3351C0.215426 38.2692 0.20058 38.1984 0.199995 38.1267C0.19941 38.0551 0.213098 37.984 0.240258 37.9177C0.267419 37.8514 0.307509 37.7911 0.358193 37.7404L4.45313 33.6455Z" fill="#0078D4"/>
|
|
||||||
<path d="M5.09381 25.0287C3.78099 26.3415 3.04346 28.1221 3.04346 29.9787C3.04346 31.8353 3.78099 33.6159 5.09381 34.9287C6.40664 36.2415 8.1872 36.9791 10.0438 36.9791C11.9004 36.9791 13.681 36.2415 14.9938 34.9287L18.2027 31.7176L8.30492 21.8198L5.09381 25.0287Z" fill="url(#paint0_linear_480_185430)"/>
|
|
||||||
<path d="M17.4209 18.1581L17.6157 18.353C17.8133 18.5505 17.9242 18.8185 17.9242 19.0978C17.9242 19.3772 17.8133 19.6451 17.6157 19.8426L13.6009 23.8574L11.918 22.1745L15.9344 18.1581C16.1319 17.9606 16.3998 17.8496 16.6792 17.8496C16.9586 17.8496 17.2265 17.9606 17.424 18.1581L17.4209 18.1581Z" fill="#C3F1FF"/>
|
|
||||||
<path d="M21.5835 22.32L21.7783 22.5149C21.9759 22.7124 22.0868 22.9803 22.0868 23.2597C22.0868 23.539 21.9759 23.807 21.7783 24.0045L17.7588 28.024L16.0759 26.3411L20.097 22.32C20.2945 22.1225 20.5624 22.0115 20.8418 22.0115C21.1212 22.0115 21.3891 22.1225 21.5866 22.32L21.5835 22.32Z" fill="#C3F1FF"/>
|
|
||||||
<path d="M20.9363 30.0618L9.87241 18.9979C9.66673 18.7922 9.33327 18.7922 9.12759 18.9979L7.67566 20.4498C7.46999 20.6555 7.46999 20.989 7.67566 21.1946L18.7395 32.2585C18.9452 32.4642 19.2787 32.4642 19.4843 32.2585L20.9363 30.8066C21.1419 30.6009 21.1419 30.2674 20.9363 30.0618Z" fill="#5EA0EF"/>
|
|
||||||
<path d="M34.9067 14.9711C36.2196 13.6583 36.9571 11.8777 36.9571 10.0211C36.9571 8.1645 36.2196 6.38393 34.9067 5.07111C33.5939 3.75829 31.8134 3.02075 29.9567 3.02075C28.1001 3.02075 26.3196 3.75829 25.0067 5.07111L21.7979 8.28222L31.6956 18.18L34.9067 14.9711Z" fill="#ECF4FD"/>
|
|
||||||
<path d="M22.5828 21.8375L22.388 21.6426C22.1904 21.4451 22.0795 21.1772 22.0795 20.8978C22.0795 20.6184 22.1904 20.3505 22.388 20.153L26.4075 16.1335L28.092 17.8179L24.0677 21.8422C23.8698 22.0376 23.6025 22.1469 23.3243 22.146C23.0461 22.1451 22.7795 22.0342 22.5828 21.8375Z" fill="#ECF4FD"/>
|
|
||||||
<path d="M18.4178 17.6802L18.2229 17.4854C18.0254 17.2878 17.9144 17.0199 17.9144 16.7406C17.9144 16.4612 18.0254 16.1933 18.2229 15.9957L22.2409 11.9778L23.9254 13.6623L19.909 17.6787C19.7115 17.8762 19.4435 17.9872 19.1642 17.9872C18.8848 17.9872 18.6169 17.8762 18.4194 17.6787L18.4178 17.6802Z" fill="#ECF4FD"/>
|
|
||||||
<path d="M19.0642 9.93799L30.1281 21.0019C30.3338 21.2075 30.6672 21.2075 30.8729 21.0019L32.3248 19.5499C32.5305 19.3443 32.5305 19.0108 32.3248 18.8051L21.261 7.74125C21.0553 7.53557 20.7218 7.53557 20.5161 7.74125L19.0642 9.19317C18.8585 9.39885 18.8585 9.73232 19.0642 9.93799Z" fill="#ECF4FD"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="paint0_linear_480_185430" x1="10.6227" y1="21.8179" x2="10.6227" y2="36.9783" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop stop-color="#5EA0EF"/>
|
|
||||||
<stop offset="0.997" stop-color="#0078D4"/>
|
|
||||||
</linearGradient>
|
|
||||||
<clipPath id="clip0_480_185430">
|
|
||||||
<rect width="40" height="40" fill="white"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.5 KiB |
@@ -1,8 +0,0 @@
|
|||||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M24 34.635C24 34.8143 23.9647 34.9918 23.8961 35.1574C23.8275 35.323 23.727 35.4734 23.6002 35.6002C23.4734 35.727 23.323 35.8275 23.1574 35.8961C22.9918 35.9647 22.8143 36 22.635 36H1.365C1.18575 36 1.00825 35.9647 0.842637 35.8961C0.677028 35.8275 0.526551 35.727 0.399799 35.6002C0.273047 35.4734 0.172502 35.323 0.103904 35.1574C0.0353068 34.9918 0 34.8143 0 34.635L0 13.365C0 13.003 0.143812 12.6558 0.399799 12.3998C0.655786 12.1438 1.00298 12 1.365 12H22.635C22.997 12 23.3442 12.1438 23.6002 12.3998C23.8562 12.6558 24 13.003 24 13.365V34.635Z" fill="#005BA1"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M30 28.635C30 28.997 29.8562 29.3442 29.6002 29.6002C29.3442 29.8562 28.997 30 28.635 30H7.365C7.00298 30 6.65579 29.8562 6.3998 29.6002C6.14381 29.3442 6 28.997 6 28.635V7.365C6 7.00298 6.14381 6.65579 6.3998 6.3998C6.65579 6.14381 7.00298 6 7.365 6H28.635C28.997 6 29.3442 6.14381 29.6002 6.3998C29.8562 6.65579 30 7.00298 30 7.365V28.635Z" fill="#5EA0EF"/>
|
|
||||||
<path d="M22.635 12H6V28.635C6 28.997 6.14381 29.3442 6.3998 29.6002C6.65579 29.8562 7.00298 30 7.365 30H24V13.365C24 13.003 23.8562 12.6558 23.6002 12.3998C23.3442 12.1438 22.997 12 22.635 12Z" fill="#0078D4"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M36 22.635C36 22.8143 35.9647 22.9918 35.8961 23.1574C35.8275 23.323 35.727 23.4734 35.6002 23.6002C35.4734 23.727 35.323 23.8275 35.1574 23.8961C34.9918 23.9647 34.8143 24 34.635 24H13.365C13.003 24 12.6558 23.8562 12.3998 23.6002C12.1438 23.3442 12 22.997 12 22.635V1.365C12 1.00298 12.1438 0.655786 12.3998 0.399799C12.6558 0.143812 13.003 0 13.365 0L34.635 0C34.8143 0 34.9918 0.0353068 35.1574 0.103904C35.323 0.172502 35.4734 0.273047 35.6002 0.399799C35.727 0.526551 35.8275 0.677028 35.8961 0.842637C35.9647 1.00825 36 1.18575 36 1.365V22.635Z" fill="#E6E7E8"/>
|
|
||||||
<path d="M22.635 12H12V22.635C12 22.997 12.1438 23.3442 12.3998 23.6002C12.6558 23.8562 13.003 24 13.365 24H24V13.365C24 13.003 23.8562 12.6558 23.6002 12.3998C23.3442 12.1438 22.997 12 22.635 12Z" fill="#BCBEC0"/>
|
|
||||||
<path d="M28.635 6H12V12H22.635C22.997 12 23.3442 12.1438 23.6002 12.3998C23.8562 12.6558 24 13.003 24 13.365V24H30V7.365C30 7.00298 29.8562 6.65579 29.6002 6.3998C29.3442 6.14381 28.997 6 28.635 6Z" fill="#D1D3D4"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.3 KiB |
@@ -1,3 +0,0 @@
|
|||||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M10 6H11V11H0V0H5V1H1V10H10V6ZM11 0V5H10V1.71094L5.35156 6.35156L4.64844 5.64844L9.28906 1H6V0H11Z" fill="#0078D4"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 229 B |
@@ -1,23 +0,0 @@
|
|||||||
<svg width="32" height="36" viewBox="0 0 32 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M30.1809 0H5.6045C5.45725 -2.06304e-07 5.31144 0.0290347 5.17541 0.0854435C5.03939 0.141852 4.91583 0.224528 4.81179 0.32874C4.70775 0.432952 4.62528 0.556655 4.5691 0.692771C4.51292 0.828887 4.48413 0.974745 4.48438 1.122V31.7149C4.48438 32.0121 4.60234 32.2972 4.81236 32.5075C5.02238 32.7178 5.30729 32.8361 5.6045 32.8365H30.1809C30.4781 32.8362 30.7631 32.7179 30.9731 32.5076C31.1832 32.2972 31.3011 32.0121 31.301 31.7149V1.122C31.301 0.824752 31.1831 0.53965 30.973 0.329288C30.763 0.118926 30.4781 0.000496739 30.1809 0V0Z" fill="url(#paint0_linear_1311_8669)"/>
|
|
||||||
<path d="M16.1495 3.22485H2.17401C1.8837 3.22485 1.60528 3.34018 1.39999 3.54546C1.19471 3.75074 1.07939 4.02917 1.07939 4.31948V34.465C1.07815 34.6087 1.10524 34.7513 1.15912 34.8845C1.21299 35.0178 1.2926 35.1391 1.39338 35.2416C1.49416 35.3441 1.61414 35.4257 1.74648 35.4818C1.87881 35.5379 2.0209 35.5674 2.16464 35.5686H26.3926C26.6829 35.5686 26.9614 35.4533 27.1667 35.248C27.3719 35.0427 27.4873 34.7643 27.4873 34.474V14.5202C27.4874 14.3764 27.4591 14.234 27.4042 14.1011C27.3492 13.9682 27.2686 13.8475 27.1669 13.7457C27.0653 13.644 26.9446 13.5633 26.8117 13.5082C26.6788 13.4532 26.5364 13.4249 26.3926 13.4249H18.3549C18.0642 13.422 17.7865 13.3045 17.5819 13.098C17.3774 12.8915 17.2626 12.6126 17.2625 12.322V4.33185C17.2628 4.03998 17.1477 3.75982 16.9423 3.55245C16.7369 3.34508 16.4579 3.22733 16.166 3.22485H16.1495Z" fill="white"/>
|
|
||||||
<path d="M16.175 3.16357H1.99513C1.69791 3.16397 1.41301 3.28232 1.20299 3.49262C0.992965 3.70292 0.875 3.98799 0.875 4.2852V34.8781C0.875 35.1753 0.992953 35.4604 1.20296 35.6708C1.41297 35.8812 1.69788 35.9996 1.99513 36.0001H26.5715C26.8687 35.9996 27.1537 35.8812 27.3637 35.6708C27.5737 35.4604 27.6916 35.1753 27.6916 34.8781V14.6307C27.6916 14.3336 27.5736 14.0487 27.3635 13.8387C27.1535 13.6286 26.8686 13.5106 26.5715 13.5106H18.4134C18.1163 13.5106 17.8314 13.3926 17.6213 13.1825C17.4113 12.9724 17.2933 12.6875 17.2933 12.3905V4.2852C17.2924 3.98858 17.1744 3.70432 16.9649 3.49426C16.7555 3.2842 16.4716 3.16535 16.175 3.16357Z" fill="url(#paint1_linear_1311_8669)"/>
|
|
||||||
<path d="M27.2629 13.7335L16.9065 3.4082V11.8213C16.9035 12.3253 17.1007 12.8097 17.4548 13.1684C17.8088 13.527 18.2907 13.7304 18.7947 13.7338L27.2629 13.7335Z" fill="#83B9F9"/>
|
|
||||||
<path d="M17.744 16.4092H4.76108C4.47196 16.4092 4.23608 16.5543 4.23608 16.7332V17.5323C4.23608 17.7112 4.47046 17.8559 4.76108 17.8559H17.744C18.0331 17.8559 18.269 17.7112 18.269 17.5323V16.7332C18.2675 16.5543 18.0331 16.4092 17.744 16.4092Z" fill="#83B9F9"/>
|
|
||||||
<path d="M17.744 20.7498H4.76108C4.47196 20.7498 4.23608 20.8945 4.23608 21.0734V21.8725C4.23608 22.0514 4.47046 22.1965 4.76108 22.1965H17.744C18.0331 22.1965 18.269 22.0514 18.269 21.8725V21.0749C18.2675 20.8945 18.0331 20.7498 17.744 20.7498Z" fill="#83B9F9"/>
|
|
||||||
<path d="M17.744 25.0906H4.76108C4.47196 25.0906 4.23608 25.2353 4.23608 25.4142V26.2126C4.23608 26.3915 4.47046 26.5366 4.76108 26.5366H17.744C18.0331 26.5366 18.269 26.3915 18.269 26.2126V25.4142C18.2675 25.2353 18.0331 25.0906 17.744 25.0906Z" fill="#83B9F9"/>
|
|
||||||
<path d="M12.4729 29.4312H4.90167C4.53492 29.4312 4.23755 29.5759 4.23755 29.7548V30.5539C4.23755 30.7328 4.53492 30.8779 4.90167 30.8779H12.4729C12.8397 30.8779 13.137 30.7328 13.137 30.5539V29.7548C13.1374 29.5759 12.8397 29.4312 12.4729 29.4312Z" fill="#83B9F9"/>
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="paint0_linear_1311_8669" x1="17.8925" y1="2.19225" x2="17.8925" y2="35.1225" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop stop-color="#DCDCDC"/>
|
|
||||||
<stop offset="1" stop-color="#AAAAAA"/>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="paint1_linear_1311_8669" x1="14.2831" y1="5.35582" x2="14.2831" y2="38.2857" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop stop-color="#0078D7"/>
|
|
||||||
<stop offset="0.327" stop-color="#0076D4"/>
|
|
||||||
<stop offset="0.576" stop-color="#0071CA"/>
|
|
||||||
<stop offset="0.799" stop-color="#0068BA"/>
|
|
||||||
<stop offset="1" stop-color="#005BA4"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.9 KiB |
@@ -1,11 +0,0 @@
|
|||||||
<svg width="30" height="34" viewBox="0 0 30 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M12.6457 18.9089H0.67782C0.520018 18.9215 0.361625 18.8893 0.235445 18.819C0.109264 18.7487 0.0249634 18.6456 0 18.5312C0.000766863 18.4748 0.0212497 18.4196 0.0595033 18.3706L14.4179 0.229509C14.4859 0.156313 14.5784 0.0970502 14.6866 0.0573734C14.7949 0.0176966 14.9152 -0.00107025 15.0362 0.00286318H29.1877C29.3456 -0.0102086 29.5043 0.0218054 29.6306 0.0922084C29.757 0.162611 29.8411 0.26595 29.8655 0.380607C29.8655 0.463721 29.823 0.54385 29.7465 0.605364L12.8941 14.7991H29.3222C29.48 14.7865 29.6384 14.8187 29.7646 14.889C29.8907 14.9593 29.975 15.0624 30 15.1768C29.998 15.2265 29.9814 15.2752 29.9516 15.3198C29.9217 15.3645 29.8791 15.4039 29.8267 15.4356L2.51725 33.5994C2.25854 33.6957 0.447568 34.6608 1.33236 33.1952L12.6457 18.9089Z" fill="url(#paint0_radial_480_182017)"/>
|
|
||||||
<defs>
|
|
||||||
<radialGradient id="paint0_radial_480_182017" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(15.0039 17.0718) scale(23.6442 13.4446)">
|
|
||||||
<stop offset="0.196" stop-color="#FFD70F"/>
|
|
||||||
<stop offset="0.438" stop-color="#FFCB12"/>
|
|
||||||
<stop offset="0.873" stop-color="#FEAC19"/>
|
|
||||||
<stop offset="1" stop-color="#FEA11B"/>
|
|
||||||
</radialGradient>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -37,8 +37,8 @@ module.exports = {
|
|||||||
global: {
|
global: {
|
||||||
branches: 25,
|
branches: 25,
|
||||||
functions: 25,
|
functions: 25,
|
||||||
lines: 29,
|
lines: 29.5,
|
||||||
statements: 29,
|
statements: 29.5,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -129,8 +129,6 @@ module.exports = {
|
|||||||
// The test environment that will be used for testing
|
// The test environment that will be used for testing
|
||||||
// testEnvironment: "jest-environment-jsdom",
|
// testEnvironment: "jest-environment-jsdom",
|
||||||
|
|
||||||
modulePaths: ["node_modules", "<rootDir>/src"],
|
|
||||||
|
|
||||||
// Options that will be passed to the testEnvironment
|
// Options that will be passed to the testEnvironment
|
||||||
// testEnvironmentOptions: {},
|
// testEnvironmentOptions: {},
|
||||||
|
|
||||||
|
|||||||
@@ -2077,7 +2077,7 @@ a:link {
|
|||||||
.resourceTreeAndTabs {
|
.resourceTreeAndTabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow-x: clip;
|
overflow-x: auto;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
@@ -2245,7 +2245,7 @@ a:link {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.refreshColHeader {
|
.refreshColHeader {
|
||||||
padding: 3px 6px 10px 0px !important;
|
padding: 3px 6px 6px 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.refreshColHeader:hover {
|
.refreshColHeader:hover {
|
||||||
@@ -2869,39 +2869,31 @@ a:link {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.settingsSection {
|
settings-pane {
|
||||||
border-bottom: 1px solid @BaseMedium;
|
.settingsSection {
|
||||||
margin-right: 24px;
|
border-bottom: 1px solid @BaseMedium;
|
||||||
padding: @MediumSpace 0px;
|
margin-right: 24px;
|
||||||
|
padding: @MediumSpace 0px;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
padding-top: 0px;
|
padding-top: 0px;
|
||||||
padding-bottom: 10px;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settingsSectionPart {
|
.settingsSectionPart {
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settingsSectionLabel {
|
.settingsSectionLabel {
|
||||||
margin-bottom: @DefaultSpace;
|
margin-bottom: @DefaultSpace;
|
||||||
margin-right: 5px;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.pageOptionsPart {
|
.pageOptionsPart {
|
||||||
padding-bottom: @MediumSpace;
|
padding-bottom: @MediumSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.legendLabel {
|
|
||||||
border-bottom: 0px;
|
|
||||||
width: auto;
|
|
||||||
font-size: @mediumFontSize;
|
|
||||||
display: inline !important;
|
|
||||||
float: left;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
32195
package-lock.json
generated
@@ -5,7 +5,7 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/arm-cosmosdb": "9.1.0",
|
"@azure/arm-cosmosdb": "9.1.0",
|
||||||
"@azure/cosmos": "3.16.1",
|
"@azure/cosmos": "3.10.5",
|
||||||
"@azure/cosmos-language-service": "0.0.5",
|
"@azure/cosmos-language-service": "0.0.5",
|
||||||
"@azure/identity": "1.2.1",
|
"@azure/identity": "1.2.1",
|
||||||
"@azure/ms-rest-nodeauth": "3.0.7",
|
"@azure/ms-rest-nodeauth": "3.0.7",
|
||||||
@@ -91,7 +91,6 @@
|
|||||||
"react-notification-system": "0.2.17",
|
"react-notification-system": "0.2.17",
|
||||||
"react-redux": "7.1.3",
|
"react-redux": "7.1.3",
|
||||||
"react-splitter-layout": "4.0.0",
|
"react-splitter-layout": "4.0.0",
|
||||||
"react-youtube": "9.0.1",
|
|
||||||
"redux": "4.0.4",
|
"redux": "4.0.4",
|
||||||
"reflect-metadata": "0.1.13",
|
"reflect-metadata": "0.1.13",
|
||||||
"rx-jupyter": "5.5.12",
|
"rx-jupyter": "5.5.12",
|
||||||
@@ -131,7 +130,6 @@
|
|||||||
"@types/sinon": "2.3.3",
|
"@types/sinon": "2.3.3",
|
||||||
"@types/styled-components": "5.1.1",
|
"@types/styled-components": "5.1.1",
|
||||||
"@types/underscore": "1.7.36",
|
"@types/underscore": "1.7.36",
|
||||||
"@types/youtube-player": "5.5.6",
|
|
||||||
"@typescript-eslint/eslint-plugin": "4.22.0",
|
"@typescript-eslint/eslint-plugin": "4.22.0",
|
||||||
"@typescript-eslint/parser": "4.22.0",
|
"@typescript-eslint/parser": "4.22.0",
|
||||||
"@webpack-cli/serve": "1.5.2",
|
"@webpack-cli/serve": "1.5.2",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"deploy": "az webapp up --name \"cosmos-explorer-preview\" --subscription \"cosmosdb-portalteam-generaltest-msft\" --resource-group \"stfaul\"",
|
"deploy": "az webapp up -n cosmos-explorer-preview --subscription cosmosdb-portalteam-generaldemo -g stfaul",
|
||||||
"start": "node index.js",
|
"start": "node index.js",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
@@ -15,4 +15,4 @@
|
|||||||
"http-proxy-middleware": "^1.1.0",
|
"http-proxy-middleware": "^1.1.0",
|
||||||
"node-fetch": "^2.6.1"
|
"node-fetch": "^2.6.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,26 @@
|
|||||||
{
|
{
|
||||||
|
"databaseId": "SampleDB",
|
||||||
|
"offerThroughput": 400,
|
||||||
|
"databaseLevelThroughput": false,
|
||||||
|
"collectionId": "Persons",
|
||||||
|
"createNewDatabase": true,
|
||||||
|
"partitionKey": { "kind": "Hash", "paths": ["/firstname"], "version": 1 },
|
||||||
"data": [
|
"data": [
|
||||||
{ "address": "2007, NE 37TH PL" },
|
{
|
||||||
{ "address": "11635, SE MAY CREEK PARK DR" },
|
"firstname": "Eva",
|
||||||
{ "address": "8923, 133RD AVE SE" },
|
"age": 44
|
||||||
{ "address": "1124, N 33RD ST" },
|
},
|
||||||
{ "address": "4288, 131ST PL SE" },
|
{
|
||||||
{ "address": "10900, SE 66TH ST" },
|
"firstname": "Véronique",
|
||||||
{ "address": "6260, 139TH AVE NE" },
|
"age": 50
|
||||||
{ "address": "13427, NE SPRING BLVD" },
|
},
|
||||||
{ "address": "13812, NE SPRING BLVD" },
|
{
|
||||||
{ "address": "5029, 159TH PL SE" },
|
"firstname": "亜妃子",
|
||||||
{ "address": "8604, 117TH AVE SE" },
|
"age": 5
|
||||||
{ "address": "1561, 139TH LN NE" },
|
},
|
||||||
{ "address": "1575, 139TH CT NE" },
|
{
|
||||||
{ "address": "13901, NE 15TH CT" },
|
"firstname": "John",
|
||||||
{ "address": "16365, NE 12TH PL" },
|
"age": 23
|
||||||
{ "address": "12226, NE 37TH ST" },
|
}
|
||||||
{ "address": "4021, 129TH CT SE" },
|
|
||||||
{ "address": "1455, 159TH PL NE" },
|
|
||||||
{ "address": "15825, NE 14TH RD" },
|
|
||||||
{ "address": "1418, 157TH CT NE" },
|
|
||||||
{ "address": "889, 131ST PL NE" }
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,10 +96,7 @@ export class Flights {
|
|||||||
public static readonly AutoscaleTest = "autoscaletest";
|
public static readonly AutoscaleTest = "autoscaletest";
|
||||||
public static readonly PartitionKeyTest = "partitionkeytest";
|
public static readonly PartitionKeyTest = "partitionkeytest";
|
||||||
public static readonly PKPartitionKeyTest = "pkpartitionkeytest";
|
public static readonly PKPartitionKeyTest = "pkpartitionkeytest";
|
||||||
public static readonly PhoenixNotebooks = "phoenixnotebooks";
|
public static readonly Phoenix = "phoenix";
|
||||||
public static readonly PhoenixFeatures = "phoenixfeatures";
|
|
||||||
public static readonly NotebooksDownBanner = "notebooksdownbanner";
|
|
||||||
public static readonly PublicGallery = "publicgallery";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AfecFeatures {
|
export class AfecFeatures {
|
||||||
@@ -346,16 +343,7 @@ export enum ConnectionStatusType {
|
|||||||
Connecting = "Connecting",
|
Connecting = "Connecting",
|
||||||
Connected = "Connected",
|
Connected = "Connected",
|
||||||
Failed = "Connection Failed",
|
Failed = "Connection Failed",
|
||||||
Reconnect = "Reconnect",
|
ReConnect = "Reconnect",
|
||||||
}
|
|
||||||
|
|
||||||
export enum ContainerStatusType {
|
|
||||||
Active = "Active",
|
|
||||||
Disconnected = "Disconnected",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum PoolIdType {
|
|
||||||
DefaultPoolId = "default",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EmulatorMasterKey =
|
export const EmulatorMasterKey =
|
||||||
@@ -368,25 +356,20 @@ export const StyleConstants = require("less-vars-loader!../../less/Common/Consta
|
|||||||
export class Notebook {
|
export class Notebook {
|
||||||
public static readonly defaultBasePath = "./notebooks";
|
public static readonly defaultBasePath = "./notebooks";
|
||||||
public static readonly heartbeatDelayMs = 60000;
|
public static readonly heartbeatDelayMs = 60000;
|
||||||
public static readonly containerStatusHeartbeatDelayMs = 30000;
|
|
||||||
public static readonly kernelRestartInitialDelayMs = 1000;
|
public static readonly kernelRestartInitialDelayMs = 1000;
|
||||||
public static readonly kernelRestartMaxDelayMs = 20000;
|
public static readonly kernelRestartMaxDelayMs = 20000;
|
||||||
public static readonly autoSaveIntervalMs = 300000;
|
public static readonly autoSaveIntervalMs = 120000;
|
||||||
public static readonly memoryGuageToGB = 1048576;
|
public static readonly memoryGuageToGB = 1048576;
|
||||||
public static readonly lowMemoryThreshold = 0.8;
|
|
||||||
public static readonly remainingTimeForAlert = 10;
|
|
||||||
public static readonly retryAttempts = 3;
|
|
||||||
public static readonly retryAttemptDelayMs = 5000;
|
|
||||||
public static readonly temporarilyDownMsg = "Notebooks is currently not available. We are working on it.";
|
public static readonly temporarilyDownMsg = "Notebooks is currently not available. We are working on it.";
|
||||||
public static readonly mongoShellTemporarilyDownMsg =
|
public static readonly mongoShellTemporarilyDownMsg =
|
||||||
"We have identified an issue with the Mongo Shell and it is unavailable right now. We are actively working on the mitigation.";
|
"We have identified an issue with the Mongo Shell and it is unavailable right now. We are actively working on the mitigation.";
|
||||||
public static readonly cassandraShellTemporarilyDownMsg =
|
public static readonly cassandraShellTemporarilyDownMsg =
|
||||||
"We have identified an issue with the Cassandra Shell and it is unavailable right now. We are actively working on the mitigation.";
|
"We have identified an issue with the Cassandra Shell and it is unavailable right now. We are actively working on the mitigation.";
|
||||||
public static saveNotebookModalTitle = "Save notebook in temporary workspace";
|
public static saveNotebookModalTitle = "Save Notebook in temporary workspace";
|
||||||
public static saveNotebookModalContent =
|
public static saveNotebookModalContent =
|
||||||
"This notebook will be saved in the temporary workspace and will be removed when the session expires.";
|
"This notebook will be saved in the temporary workspace and will be removed when the session expires. To save your work permanently, save your notebooks to a GitHub repository or download the notebooks to your local machine before the session ends.";
|
||||||
public static newNotebookModalTitle = "Create notebook in temporary workspace";
|
public static newNotebookModalTitle = "Create Notebook in temporary workspace";
|
||||||
public static newNotebookUploadModalTitle = "Upload notebook to temporary workspace";
|
public static newNotebookUploadModalTitle = "Upload Notebook in temporary workspace";
|
||||||
public static newNotebookModalContent1 =
|
public static newNotebookModalContent1 =
|
||||||
"A temporary workspace will be created to enable you to work with notebooks. When the session expires, any notebooks in the workspace will be removed.";
|
"A temporary workspace will be created to enable you to work with notebooks. When the session expires, any notebooks in the workspace will be removed.";
|
||||||
public static newNotebookModalContent2 =
|
public static newNotebookModalContent2 =
|
||||||
@@ -418,11 +401,3 @@ export class TerminalQueryParams {
|
|||||||
public static readonly SubscriptionId = "subscriptionId";
|
public static readonly SubscriptionId = "subscriptionId";
|
||||||
public static readonly TerminalEndpoint = "terminalEndpoint";
|
public static readonly TerminalEndpoint = "terminalEndpoint";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class JunoEndpoints {
|
|
||||||
public static readonly Test = "https://juno-test.documents-dev.windows-int.net";
|
|
||||||
public static readonly Test2 = "https://juno-test2.documents-dev.windows-int.net";
|
|
||||||
public static readonly Test3 = "https://juno-test3.documents-dev.windows-int.net";
|
|
||||||
public static readonly Prod = "https://tools.cosmos.azure.com";
|
|
||||||
public static readonly Stage = "https://tools-staging.cosmos.azure.com";
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ResourceType } from "@azure/cosmos";
|
import { ResourceType } from "@azure/cosmos/dist-esm/common/constants";
|
||||||
import { Platform, resetConfigContext, updateConfigContext } from "../ConfigContext";
|
import { Platform, resetConfigContext, updateConfigContext } from "../ConfigContext";
|
||||||
import { updateUserContext } from "../UserContext";
|
import { updateUserContext } from "../UserContext";
|
||||||
import { endpoint, getTokenFromAuthService, requestPlugin, tokenProvider } from "./CosmosClient";
|
import { endpoint, getTokenFromAuthService, requestPlugin, tokenProvider } from "./CosmosClient";
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as Cosmos from "@azure/cosmos";
|
import * as Cosmos from "@azure/cosmos";
|
||||||
|
import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos";
|
||||||
import { configContext, Platform } from "../ConfigContext";
|
import { configContext, Platform } from "../ConfigContext";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||||
@@ -7,7 +8,7 @@ import { getErrorMessage } from "./ErrorHandlingUtils";
|
|||||||
|
|
||||||
const _global = typeof self === "undefined" ? window : self;
|
const _global = typeof self === "undefined" ? window : self;
|
||||||
|
|
||||||
export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
export const tokenProvider = async (requestInfo: RequestInfo) => {
|
||||||
const { verb, resourceId, resourceType, headers } = requestInfo;
|
const { verb, resourceId, resourceType, headers } = requestInfo;
|
||||||
|
|
||||||
if (userContext.features.enableAadDataPlane && userContext.aadToken) {
|
if (userContext.features.enableAadDataPlane && userContext.aadToken) {
|
||||||
@@ -18,13 +19,13 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
|||||||
|
|
||||||
if (configContext.platform === Platform.Emulator) {
|
if (configContext.platform === Platform.Emulator) {
|
||||||
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
|
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
|
||||||
await Cosmos.setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
|
await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
|
||||||
return decodeURIComponent(headers.authorization);
|
return decodeURIComponent(headers.authorization);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userContext.masterKey) {
|
if (userContext.masterKey) {
|
||||||
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
|
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
|
||||||
await Cosmos.setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
|
await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
|
||||||
return decodeURIComponent(headers.authorization);
|
return decodeURIComponent(headers.authorization);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,21 +77,10 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Capability is a bitmap, which cosmosdb backend decodes as per the below enum
|
|
||||||
enum SDKSupportedCapabilities {
|
|
||||||
None = 0,
|
|
||||||
PartitionMerge = 1 << 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
let _client: Cosmos.CosmosClient;
|
let _client: Cosmos.CosmosClient;
|
||||||
|
|
||||||
export function client(): Cosmos.CosmosClient {
|
export function client(): Cosmos.CosmosClient {
|
||||||
if (_client) return _client;
|
if (_client) return _client;
|
||||||
|
|
||||||
let _defaultHeaders: Cosmos.CosmosHeaders = {};
|
|
||||||
_defaultHeaders["x-ms-cosmos-sdk-supportedcapabilities"] =
|
|
||||||
SDKSupportedCapabilities.None | SDKSupportedCapabilities.PartitionMerge;
|
|
||||||
|
|
||||||
const options: Cosmos.CosmosClientOptions = {
|
const options: Cosmos.CosmosClientOptions = {
|
||||||
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
|
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
|
||||||
key: userContext.masterKey,
|
key: userContext.masterKey,
|
||||||
@@ -99,7 +89,6 @@ export function client(): Cosmos.CosmosClient {
|
|||||||
enableEndpointDiscovery: false,
|
enableEndpointDiscovery: false,
|
||||||
},
|
},
|
||||||
userAgentSuffix: "Azure Portal",
|
userAgentSuffix: "Azure Portal",
|
||||||
defaultHeaders: _defaultHeaders,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (configContext.PROXY_PATH !== undefined) {
|
if (configContext.PROXY_PATH !== undefined) {
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ const fetchMock = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const partitionKeyProperties = ["pk"];
|
const partitionKeyProperty = "pk";
|
||||||
|
|
||||||
const collection = {
|
const collection = {
|
||||||
id: () => "testCollection",
|
id: () => "testCollection",
|
||||||
rid: "testCollectionrid",
|
rid: "testCollectionrid",
|
||||||
partitionKeyProperties,
|
partitionKeyProperty,
|
||||||
partitionKey: {
|
partitionKey: {
|
||||||
paths: ["/pk"],
|
paths: ["/pk"],
|
||||||
kind: "Hash",
|
kind: "Hash",
|
||||||
@@ -41,7 +41,7 @@ const collection = {
|
|||||||
const documentId = ({
|
const documentId = ({
|
||||||
partitionKeyHeader: () => "[]",
|
partitionKeyHeader: () => "[]",
|
||||||
self: "db/testDB/db/testCollection/docs/testId",
|
self: "db/testDB/db/testCollection/docs/testId",
|
||||||
partitionKeyProperties,
|
partitionKeyProperty,
|
||||||
partitionKey: {
|
partitionKey: {
|
||||||
paths: ["/pk"],
|
paths: ["/pk"],
|
||||||
kind: "Hash",
|
kind: "Hash",
|
||||||
@@ -236,12 +236,13 @@ describe("MongoProxyClient", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("returns a production endpoint", () => {
|
it("returns a production endpoint", () => {
|
||||||
const endpoint = getEndpoint("https://main.documentdb.ext.azure.com");
|
const endpoint = getEndpoint();
|
||||||
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/mongo/explorer");
|
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/mongo/explorer");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns a development endpoint", () => {
|
it("returns a development endpoint", () => {
|
||||||
const endpoint = getEndpoint("https://localhost:1234");
|
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" });
|
||||||
|
const endpoint = getEndpoint();
|
||||||
expect(endpoint).toEqual("https://localhost:1234/api/mongo/explorer");
|
expect(endpoint).toEqual("https://localhost:1234/api/mongo/explorer");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -249,7 +250,7 @@ describe("MongoProxyClient", () => {
|
|||||||
updateUserContext({
|
updateUserContext({
|
||||||
authType: AuthType.EncryptedToken,
|
authType: AuthType.EncryptedToken,
|
||||||
});
|
});
|
||||||
const endpoint = getEndpoint("https://main.documentdb.ext.azure.com");
|
const endpoint = getEndpoint();
|
||||||
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
|
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
||||||
import queryString from "querystring";
|
import queryString from "querystring";
|
||||||
import { allowedMongoProxyEndpoints, validateEndpoint } from "Utils/EndpointValidation";
|
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
@@ -76,7 +75,7 @@ export function queryDocuments(
|
|||||||
dba: databaseAccount.name,
|
dba: databaseAccount.name,
|
||||||
pk:
|
pk:
|
||||||
collection && collection.partitionKey && !collection.partitionKey.systemKey
|
collection && collection.partitionKey && !collection.partitionKey.systemKey
|
||||||
? collection.partitionKeyProperties?.[0]
|
? collection.partitionKeyProperty
|
||||||
: "",
|
: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -139,7 +138,7 @@ export function readDocument(
|
|||||||
dba: databaseAccount.name,
|
dba: databaseAccount.name,
|
||||||
pk:
|
pk:
|
||||||
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
|
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
|
||||||
? documentId.partitionKeyProperties?.[0]
|
? documentId.partitionKeyProperty
|
||||||
: "",
|
: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -225,7 +224,7 @@ export function updateDocument(
|
|||||||
dba: databaseAccount.name,
|
dba: databaseAccount.name,
|
||||||
pk:
|
pk:
|
||||||
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
|
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
|
||||||
? documentId.partitionKeyProperties?.[0]
|
? documentId.partitionKeyProperty
|
||||||
: "",
|
: "",
|
||||||
};
|
};
|
||||||
const endpoint = getFeatureEndpointOrDefault("updateDocument");
|
const endpoint = getFeatureEndpointOrDefault("updateDocument");
|
||||||
@@ -266,7 +265,7 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
|
|||||||
dba: databaseAccount.name,
|
dba: databaseAccount.name,
|
||||||
pk:
|
pk:
|
||||||
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
|
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
|
||||||
? documentId.partitionKeyProperties?.[0]
|
? documentId.partitionKeyProperty
|
||||||
: "",
|
: "",
|
||||||
};
|
};
|
||||||
const endpoint = getFeatureEndpointOrDefault("deleteDocument");
|
const endpoint = getFeatureEndpointOrDefault("deleteDocument");
|
||||||
@@ -337,17 +336,14 @@ export function createMongoCollectionWithProxy(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getFeatureEndpointOrDefault(feature: string): string {
|
export function getFeatureEndpointOrDefault(feature: string): string {
|
||||||
const endpoint =
|
return hasFlag(userContext.features.mongoProxyAPIs, feature)
|
||||||
hasFlag(userContext.features.mongoProxyAPIs, feature) &&
|
? getEndpoint(userContext.features.mongoProxyEndpoint)
|
||||||
validateEndpoint(userContext.features.mongoProxyEndpoint, allowedMongoProxyEndpoints)
|
: getEndpoint();
|
||||||
? userContext.features.mongoProxyEndpoint
|
|
||||||
: configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT;
|
|
||||||
|
|
||||||
return getEndpoint(endpoint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEndpoint(endpoint: string): string {
|
export function getEndpoint(customEndpoint?: string): string {
|
||||||
let url = endpoint + "/api/mongo/explorer";
|
let url = customEndpoint ? customEndpoint : configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT;
|
||||||
|
url += "/api/mongo/explorer";
|
||||||
|
|
||||||
if (userContext.authType === AuthType.EncryptedToken) {
|
if (userContext.authType === AuthType.EncryptedToken) {
|
||||||
url = url.replace("api/mongo", "api/guest/mongo");
|
url = url.replace("api/mongo", "api/guest/mongo");
|
||||||
|
|||||||
@@ -149,10 +149,10 @@ export class QueriesClient {
|
|||||||
const documentId = new DocumentId(
|
const documentId = new DocumentId(
|
||||||
{
|
{
|
||||||
partitionKey: QueriesClient.PartitionKey,
|
partitionKey: QueriesClient.PartitionKey,
|
||||||
partitionKeyProperties: ["id"],
|
partitionKeyProperty: "id",
|
||||||
} as DocumentsTab,
|
} as DocumentsTab,
|
||||||
query,
|
query,
|
||||||
[query.queryName]
|
query.queryName
|
||||||
); // TODO: Remove DocumentId's dependency on DocumentsTab
|
); // TODO: Remove DocumentId's dependency on DocumentsTab
|
||||||
const options: any = { partitionKey: query.resourceId };
|
const options: any = { partitionKey: query.resourceId };
|
||||||
return deleteDocument(queriesCollection, documentId)
|
return deleteDocument(queriesCollection, documentId)
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { ContainerRequest, ContainerResponse, DatabaseRequest, DatabaseResponse, RequestOptions } from "@azure/cosmos";
|
import { ContainerResponse, DatabaseResponse } from "@azure/cosmos";
|
||||||
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
|
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
|
||||||
|
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { useDatabases } from "../../Explorer/useDatabases";
|
import { useDatabases } from "../../Explorer/useDatabases";
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { DatabaseRequest, DatabaseResponse } from "@azure/cosmos";
|
import { DatabaseResponse } from "@azure/cosmos";
|
||||||
|
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { useDatabases } from "../../Explorer/useDatabases";
|
import { useDatabases } from "../../Explorer/useDatabases";
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { CollectionBase } from "../../Contracts/ViewModels";
|
import { CollectionBase } from "../../Contracts/ViewModels";
|
||||||
import DocumentId from "../../Explorer/Tree/DocumentId";
|
|
||||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { getEntityName } from "../DocumentUtility";
|
import { getEntityName } from "../DocumentUtility";
|
||||||
import { handleError } from "../ErrorHandlingUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import DocumentId from "../../Explorer/Tree/DocumentId";
|
||||||
|
|
||||||
export const deleteDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<void> => {
|
export const deleteDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<void> => {
|
||||||
const entityName: string = getEntityName();
|
const entityName: string = getEntityName();
|
||||||
@@ -13,7 +13,7 @@ export const deleteDocument = async (collection: CollectionBase, documentId: Doc
|
|||||||
await client()
|
await client()
|
||||||
.database(collection.databaseId)
|
.database(collection.databaseId)
|
||||||
.container(collection.id())
|
.container(collection.id())
|
||||||
.item(documentId.id(), documentId.partitionKeyValue?.length === 0 ? undefined : documentId.partitionKeyValue)
|
.item(documentId.id(), documentId.partitionKeyValue)
|
||||||
.delete();
|
.delete();
|
||||||
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
|
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,29 +1,21 @@
|
|||||||
import { Item, RequestOptions } from "@azure/cosmos";
|
import { Item } from "@azure/cosmos";
|
||||||
import { CollectionBase } from "../../Contracts/ViewModels";
|
import { CollectionBase } from "../../Contracts/ViewModels";
|
||||||
import DocumentId from "../../Explorer/Tree/DocumentId";
|
|
||||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { HttpHeaders } from "../Constants";
|
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { getEntityName } from "../DocumentUtility";
|
import { getEntityName } from "../DocumentUtility";
|
||||||
import { handleError } from "../ErrorHandlingUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import DocumentId from "../../Explorer/Tree/DocumentId";
|
||||||
|
|
||||||
export const readDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<Item> => {
|
export const readDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<Item> => {
|
||||||
const entityName = getEntityName();
|
const entityName = getEntityName();
|
||||||
const clearMessage = logConsoleProgress(`Reading ${entityName} ${documentId.id()}`);
|
const clearMessage = logConsoleProgress(`Reading ${entityName} ${documentId.id()}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const options: RequestOptions =
|
|
||||||
documentId.partitionKey.kind === "MultiHash"
|
|
||||||
? {
|
|
||||||
[HttpHeaders.partitionKey]: documentId.partitionKeyValue,
|
|
||||||
}
|
|
||||||
: {};
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(collection.databaseId)
|
.database(collection.databaseId)
|
||||||
.container(collection.id())
|
.container(collection.id())
|
||||||
// use undefined if the partitionKeyValue is empty
|
.item(documentId.id(), documentId.partitionKeyValue)
|
||||||
.item(documentId.id(), documentId.partitionKeyValue?.length === 0 ? undefined : documentId.partitionKeyValue)
|
.read();
|
||||||
.read(options);
|
|
||||||
|
|
||||||
return response?.resource;
|
return response?.resource;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { RequestOptions } from "@azure/cosmos";
|
|
||||||
import { Offer } from "../../Contracts/DataModels";
|
|
||||||
import { HttpHeaders } from "../Constants";
|
import { HttpHeaders } from "../Constants";
|
||||||
|
import { Offer } from "../../Contracts/DataModels";
|
||||||
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { parseSDKOfferResponse } from "../OfferUtility";
|
import { parseSDKOfferResponse } from "../OfferUtility";
|
||||||
import { readOffers } from "./readOffers";
|
import { readOffers } from "./readOffers";
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ContainerDefinition, RequestOptions } from "@azure/cosmos";
|
import { ContainerDefinition } from "@azure/cosmos";
|
||||||
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { Collection } from "../../Contracts/DataModels";
|
import { Collection } from "../../Contracts/DataModels";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { Item, RequestOptions } from "@azure/cosmos";
|
|
||||||
import { HttpHeaders } from "Common/Constants";
|
|
||||||
import { CollectionBase } from "../../Contracts/ViewModels";
|
import { CollectionBase } from "../../Contracts/ViewModels";
|
||||||
import DocumentId from "../../Explorer/Tree/DocumentId";
|
import { Item } from "@azure/cosmos";
|
||||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { getEntityName } from "../DocumentUtility";
|
import { getEntityName } from "../DocumentUtility";
|
||||||
import { handleError } from "../ErrorHandlingUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import DocumentId from "../../Explorer/Tree/DocumentId";
|
||||||
|
|
||||||
export const updateDocument = async (
|
export const updateDocument = async (
|
||||||
collection: CollectionBase,
|
collection: CollectionBase,
|
||||||
@@ -16,17 +15,11 @@ export const updateDocument = async (
|
|||||||
const clearMessage = logConsoleProgress(`Updating ${entityName} ${documentId.id()}`);
|
const clearMessage = logConsoleProgress(`Updating ${entityName} ${documentId.id()}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const options: RequestOptions =
|
|
||||||
documentId.partitionKey.kind === "MultiHash"
|
|
||||||
? {
|
|
||||||
[HttpHeaders.partitionKey]: documentId.partitionKeyValue,
|
|
||||||
}
|
|
||||||
: {};
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(collection.databaseId)
|
.database(collection.databaseId)
|
||||||
.container(collection.id())
|
.container(collection.id())
|
||||||
.item(documentId.id(), documentId.partitionKeyValue?.length === 0 ? undefined : documentId.partitionKeyValue)
|
.item(documentId.id(), documentId.partitionKeyValue)
|
||||||
.replace(newDocument, options);
|
.replace(newDocument);
|
||||||
|
|
||||||
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
|
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
|
||||||
return response?.resource;
|
return response?.resource;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { OfferDefinition, RequestOptions } from "@azure/cosmos";
|
import { OfferDefinition } from "@azure/cosmos";
|
||||||
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { Offer, SDKOfferDefinition, UpdateOfferParams } from "../../Contracts/DataModels";
|
import { Offer, SDKOfferDefinition, UpdateOfferParams } from "../../Contracts/DataModels";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|||||||
@@ -1,17 +1,3 @@
|
|||||||
import {
|
|
||||||
allowedAadEndpoints,
|
|
||||||
allowedArcadiaEndpoints,
|
|
||||||
allowedArmEndpoints,
|
|
||||||
allowedBackendEndpoints,
|
|
||||||
allowedEmulatorEndpoints,
|
|
||||||
allowedGraphEndpoints,
|
|
||||||
allowedHostedExplorerEndpoints,
|
|
||||||
allowedJunoOrigins,
|
|
||||||
allowedMongoBackendEndpoints,
|
|
||||||
allowedMsalRedirectEndpoints,
|
|
||||||
validateEndpoint,
|
|
||||||
} from "Utils/EndpointValidation";
|
|
||||||
|
|
||||||
export enum Platform {
|
export enum Platform {
|
||||||
Portal = "Portal",
|
Portal = "Portal",
|
||||||
Hosted = "Hosted",
|
Hosted = "Hosted",
|
||||||
@@ -20,7 +6,7 @@ export enum Platform {
|
|||||||
|
|
||||||
export interface ConfigContext {
|
export interface ConfigContext {
|
||||||
platform: Platform;
|
platform: Platform;
|
||||||
allowedParentFrameOrigins: ReadonlyArray<string>;
|
allowedParentFrameOrigins: string[];
|
||||||
gitSha?: string;
|
gitSha?: string;
|
||||||
proxyPath?: string;
|
proxyPath?: string;
|
||||||
AAD_ENDPOINT: string;
|
AAD_ENDPOINT: string;
|
||||||
@@ -37,12 +23,10 @@ export interface ConfigContext {
|
|||||||
PROXY_PATH?: string;
|
PROXY_PATH?: string;
|
||||||
JUNO_ENDPOINT: string;
|
JUNO_ENDPOINT: string;
|
||||||
GITHUB_CLIENT_ID: string;
|
GITHUB_CLIENT_ID: string;
|
||||||
GITHUB_TEST_ENV_CLIENT_ID: string;
|
|
||||||
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
|
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
|
||||||
isTerminalEnabled: boolean;
|
|
||||||
isPhoenixEnabled: boolean;
|
|
||||||
hostedExplorerURL: string;
|
hostedExplorerURL: string;
|
||||||
armAPIVersion?: string;
|
armAPIVersion?: string;
|
||||||
|
allowedJunoOrigins: string[];
|
||||||
msalRedirectURI?: string;
|
msalRedirectURI?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,11 +36,12 @@ let configContext: Readonly<ConfigContext> = {
|
|||||||
allowedParentFrameOrigins: [
|
allowedParentFrameOrigins: [
|
||||||
`^https:\\/\\/cosmos\\.azure\\.(com|cn|us)$`,
|
`^https:\\/\\/cosmos\\.azure\\.(com|cn|us)$`,
|
||||||
`^https:\\/\\/[\\.\\w]*portal\\.azure\\.(com|cn|us)$`,
|
`^https:\\/\\/[\\.\\w]*portal\\.azure\\.(com|cn|us)$`,
|
||||||
`^https:\\/\\/[\\.\\w]*portal\\.microsoftazure\\.de$`,
|
`^https:\\/\\/[\\.\\w]*portal\\.microsoftazure.de$`,
|
||||||
`^https:\\/\\/[\\.\\w]*ext\\.azure\\.(com|cn|us)$`,
|
`^https:\\/\\/[\\.\\w]*ext\\.azure\\.(com|cn|us)$`,
|
||||||
`^https:\\/\\/[\\.\\w]*\\.ext\\.microsoftazure\\.de$`,
|
`^https:\\/\\/[\\.\\w]*\\.ext\\.microsoftazure\\.de$`,
|
||||||
`^https:\\/\\/cosmos-db-dataexplorer-germanycentral\\.azurewebsites\\.de$`,
|
`^https://cosmos-db-dataexplorer-germanycentral.azurewebsites.de$`,
|
||||||
], // Webpack injects this at build time
|
],
|
||||||
|
// Webpack injects this at build time
|
||||||
gitSha: process.env.GIT_SHA,
|
gitSha: process.env.GIT_SHA,
|
||||||
hostedExplorerURL: "https://cosmos.azure.com/",
|
hostedExplorerURL: "https://cosmos.azure.com/",
|
||||||
AAD_ENDPOINT: "https://login.microsoftonline.com/",
|
AAD_ENDPOINT: "https://login.microsoftonline.com/",
|
||||||
@@ -67,12 +52,16 @@ let configContext: Readonly<ConfigContext> = {
|
|||||||
GRAPH_API_VERSION: "1.6",
|
GRAPH_API_VERSION: "1.6",
|
||||||
ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net",
|
ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net",
|
||||||
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
|
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
|
||||||
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1189306
|
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306
|
||||||
GITHUB_TEST_ENV_CLIENT_ID: "b63fc8cbf87fd3c6e2eb", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1777772
|
|
||||||
JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
|
JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
|
||||||
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
||||||
isTerminalEnabled: false,
|
allowedJunoOrigins: [
|
||||||
isPhoenixEnabled: false,
|
"https://juno-test.documents-dev.windows-int.net",
|
||||||
|
"https://juno-test2.documents-dev.windows-int.net",
|
||||||
|
"https://tools.cosmos.azure.com",
|
||||||
|
"https://tools-staging.cosmos.azure.com",
|
||||||
|
"https://localhost",
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function resetConfigContext(): void {
|
export function resetConfigContext(): void {
|
||||||
@@ -83,50 +72,6 @@ export function resetConfigContext(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function updateConfigContext(newContext: Partial<ConfigContext>): void {
|
export function updateConfigContext(newContext: Partial<ConfigContext>): void {
|
||||||
if (!newContext) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.ARM_ENDPOINT, allowedArmEndpoints)) {
|
|
||||||
delete newContext.ARM_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.AAD_ENDPOINT, allowedAadEndpoints)) {
|
|
||||||
delete newContext.AAD_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.EMULATOR_ENDPOINT, allowedEmulatorEndpoints)) {
|
|
||||||
delete newContext.EMULATOR_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.GRAPH_ENDPOINT, allowedGraphEndpoints)) {
|
|
||||||
delete newContext.GRAPH_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.ARCADIA_ENDPOINT, allowedArcadiaEndpoints)) {
|
|
||||||
delete newContext.ARCADIA_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.BACKEND_ENDPOINT, allowedBackendEndpoints)) {
|
|
||||||
delete newContext.BACKEND_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.MONGO_BACKEND_ENDPOINT, allowedMongoBackendEndpoints)) {
|
|
||||||
delete newContext.MONGO_BACKEND_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.JUNO_ENDPOINT, allowedJunoOrigins)) {
|
|
||||||
delete newContext.JUNO_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.hostedExplorerURL, allowedHostedExplorerEndpoints)) {
|
|
||||||
delete newContext.hostedExplorerURL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.msalRedirectURI, allowedMsalRedirectEndpoints)) {
|
|
||||||
delete newContext.msalRedirectURI;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(configContext, newContext);
|
Object.assign(configContext, newContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,8 +95,18 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
|
|||||||
});
|
});
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
try {
|
try {
|
||||||
const { ...externalConfig } = await response.json();
|
const { allowedParentFrameOrigins, allowedJunoOrigins, ...externalConfig } = await response.json();
|
||||||
updateConfigContext(externalConfig);
|
Object.assign(configContext, externalConfig);
|
||||||
|
if (allowedParentFrameOrigins && allowedParentFrameOrigins.length > 0) {
|
||||||
|
updateConfigContext({
|
||||||
|
allowedParentFrameOrigins: [...configContext.allowedParentFrameOrigins, ...allowedParentFrameOrigins],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (allowedJunoOrigins && allowedJunoOrigins.length > 0) {
|
||||||
|
updateConfigContext({
|
||||||
|
allowedJunoOrigins: [...configContext.allowedJunoOrigins, ...allowedJunoOrigins],
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Unable to parse json in config file");
|
console.error("Unable to parse json in config file");
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ConnectionStatusType, ContainerStatusType } from "../Common/Constants";
|
import { ConnectionStatusType } from "../Common/Constants";
|
||||||
|
|
||||||
export interface DatabaseAccount {
|
export interface DatabaseAccount {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -7,11 +7,6 @@ export interface DatabaseAccount {
|
|||||||
type: string;
|
type: string;
|
||||||
kind: string;
|
kind: string;
|
||||||
properties: DatabaseAccountExtendedProperties;
|
properties: DatabaseAccountExtendedProperties;
|
||||||
systemData?: DatabaseAccountSystemData;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DatabaseAccountSystemData {
|
|
||||||
createdAt: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatabaseAccountExtendedProperties {
|
export interface DatabaseAccountExtendedProperties {
|
||||||
@@ -31,8 +26,6 @@ export interface DatabaseAccountExtendedProperties {
|
|||||||
isVirtualNetworkFilterEnabled?: boolean;
|
isVirtualNetworkFilterEnabled?: boolean;
|
||||||
ipRules?: IpRule[];
|
ipRules?: IpRule[];
|
||||||
privateEndpointConnections?: unknown[];
|
privateEndpointConnections?: unknown[];
|
||||||
capacity?: { totalThroughputLimit: number };
|
|
||||||
locations?: DatabaseAccountResponseLocation[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatabaseAccountResponseLocation {
|
export interface DatabaseAccountResponseLocation {
|
||||||
@@ -433,57 +426,6 @@ export interface OperationStatus {
|
|||||||
export interface NotebookWorkspaceConnectionInfo {
|
export interface NotebookWorkspaceConnectionInfo {
|
||||||
authToken: string;
|
authToken: string;
|
||||||
notebookServerEndpoint: string;
|
notebookServerEndpoint: string;
|
||||||
forwardingId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ContainerInfo {
|
|
||||||
durationLeftInMinutes: number;
|
|
||||||
phoenixServerInfo: NotebookWorkspaceConnectionInfo;
|
|
||||||
status: ContainerStatusType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IProvisionData {
|
|
||||||
cosmosEndpoint: string;
|
|
||||||
poolId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IContainerData {
|
|
||||||
forwardingId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IDbAccountAllow {
|
|
||||||
status: number;
|
|
||||||
message?: string;
|
|
||||||
type?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IResponse<T> {
|
|
||||||
status: number;
|
|
||||||
data: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IPhoenixError {
|
|
||||||
message: string;
|
|
||||||
type: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMaxAllocationTimeExceeded extends IPhoenixError {
|
|
||||||
earliestAllocationTimestamp: string;
|
|
||||||
maxAllocationTimePerDayPerUserInMinutes: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMaxDbAccountsPerUserExceeded extends IPhoenixError {
|
|
||||||
maxSimultaneousConnectionsPerUser: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMaxUsersPerDbAccountExceeded extends IPhoenixError {
|
|
||||||
maxSimultaneousUsersPerDbAccount: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IPhoenixConnectionInfoResult {
|
|
||||||
readonly authToken?: string;
|
|
||||||
readonly phoenixServiceUrl?: string;
|
|
||||||
readonly forwardingId?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NotebookWorkspaceFeedResponse {
|
export interface NotebookWorkspaceFeedResponse {
|
||||||
@@ -561,14 +503,3 @@ export interface ContainerConnectionInfo {
|
|||||||
status: ConnectionStatusType;
|
status: ConnectionStatusType;
|
||||||
//need to add ram and rom info
|
//need to add ram and rom info
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PhoenixErrorType {
|
|
||||||
MaxAllocationTimeExceeded = "MaxAllocationTimeExceeded",
|
|
||||||
MaxDbAccountsPerUserExceeded = "MaxDbAccountsPerUserExceeded",
|
|
||||||
MaxUsersPerDbAccountExceeded = "MaxUsersPerDbAccountExceeded",
|
|
||||||
AllocationValidationResult = "AllocationValidationResult",
|
|
||||||
RegionNotServicable = "RegionNotServicable",
|
|
||||||
SubscriptionNotAllowed = "SubscriptionNotAllowed",
|
|
||||||
UnknownError = "UnknownError",
|
|
||||||
PhoenixFlightFallback = "PhoenixFlightFallback",
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ export enum MessageTypes {
|
|||||||
CreateWorkspace,
|
CreateWorkspace,
|
||||||
CreateSparkPool,
|
CreateSparkPool,
|
||||||
RefreshDatabaseAccount,
|
RefreshDatabaseAccount,
|
||||||
CloseTab,
|
|
||||||
OpenQuickstartBlade,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Versions, ActionContracts, Diagnostics };
|
export { Versions, ActionContracts, Diagnostics };
|
||||||
|
|||||||
@@ -86,7 +86,6 @@ export interface Database extends TreeNode {
|
|||||||
offer: ko.Observable<DataModels.Offer>;
|
offer: ko.Observable<DataModels.Offer>;
|
||||||
isDatabaseExpanded: ko.Observable<boolean>;
|
isDatabaseExpanded: ko.Observable<boolean>;
|
||||||
isDatabaseShared: ko.Computed<boolean>;
|
isDatabaseShared: ko.Computed<boolean>;
|
||||||
isSampleDB?: boolean;
|
|
||||||
|
|
||||||
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
|
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
|
||||||
|
|
||||||
@@ -107,13 +106,12 @@ export interface CollectionBase extends TreeNode {
|
|||||||
self: string;
|
self: string;
|
||||||
rawDataModel: DataModels.Collection;
|
rawDataModel: DataModels.Collection;
|
||||||
partitionKey: DataModels.PartitionKey;
|
partitionKey: DataModels.PartitionKey;
|
||||||
partitionKeyProperties: string[];
|
partitionKeyProperty: string;
|
||||||
partitionKeyPropertyHeaders: string[];
|
partitionKeyPropertyHeader: string;
|
||||||
id: ko.Observable<string>;
|
id: ko.Observable<string>;
|
||||||
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
|
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
|
||||||
children: ko.ObservableArray<TreeNode>;
|
children: ko.ObservableArray<TreeNode>;
|
||||||
isCollectionExpanded: ko.Observable<boolean>;
|
isCollectionExpanded: ko.Observable<boolean>;
|
||||||
isSampleCollection?: boolean;
|
|
||||||
|
|
||||||
onDocumentDBDocumentsClick(): void;
|
onDocumentDBDocumentsClick(): void;
|
||||||
onNewQueryClick(source: any, event?: MouseEvent, queryText?: string): void;
|
onNewQueryClick(source: any, event?: MouseEvent, queryText?: string): void;
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const createDatabaseContextMenu = (container: Explorer, databaseId: strin
|
|||||||
const items: TreeNodeMenuItem[] = [
|
const items: TreeNodeMenuItem[] = [
|
||||||
{
|
{
|
||||||
iconSrc: AddCollectionIcon,
|
iconSrc: AddCollectionIcon,
|
||||||
onClick: () => container.onNewCollectionClicked({ databaseId }),
|
onClick: () => container.onNewCollectionClicked(databaseId),
|
||||||
label: `New ${getCollectionName()}`,
|
label: `New ${getCollectionName()}`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -83,6 +83,7 @@ export const createCollectionContextMenuButton = (
|
|||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
iconSrc: HostedTerminalIcon,
|
iconSrc: HostedTerminalIcon,
|
||||||
|
isDisabled: useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
if (useNotebook.getState().isShellEnabled) {
|
if (useNotebook.getState().isShellEnabled) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
Link,
|
Link,
|
||||||
PrimaryButton,
|
PrimaryButton,
|
||||||
ProgressIndicator,
|
ProgressIndicator,
|
||||||
|
Text,
|
||||||
TextField,
|
TextField,
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
@@ -196,7 +197,7 @@ export const Dialog: FC = () => {
|
|||||||
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
|
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
{contentHtml}
|
{contentHtml && <Text>{contentHtml}</Text>}
|
||||||
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
|
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<PrimaryButton {...primaryButtonProps} />
|
<PrimaryButton {...primaryButtonProps} />
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { DefaultButton, IButtonProps, ITextFieldProps, TextField } from "@fluentui/react";
|
import { DefaultButton, IButtonProps, ITextFieldProps, TextField } from "@fluentui/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import * as UrlUtility from "../../../Common/UrlUtility";
|
|
||||||
import { IGitHubRepo } from "../../../GitHub/GitHubClient";
|
|
||||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
|
||||||
import Explorer from "../../Explorer";
|
|
||||||
import { RepoListItem } from "./GitHubReposComponent";
|
import { RepoListItem } from "./GitHubReposComponent";
|
||||||
import { ChildrenMargin } from "./GitHubStyleConstants";
|
import { ChildrenMargin } from "./GitHubStyleConstants";
|
||||||
|
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||||
|
import { IGitHubRepo } from "../../../GitHub/GitHubClient";
|
||||||
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import * as UrlUtility from "../../../Common/UrlUtility";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
|
||||||
export interface AddRepoComponentProps {
|
export interface AddRepoComponentProps {
|
||||||
container: Explorer;
|
container: Explorer;
|
||||||
@@ -27,6 +27,7 @@ export class AddRepoComponent extends React.Component<AddRepoComponentProps, Add
|
|||||||
private static readonly ButtonText = "Add";
|
private static readonly ButtonText = "Add";
|
||||||
private static readonly TextFieldPlaceholder = "https://github.com/owner/repo/tree/branch";
|
private static readonly TextFieldPlaceholder = "https://github.com/owner/repo/tree/branch";
|
||||||
private static readonly TextFieldErrorMessage = "Invalid url";
|
private static readonly TextFieldErrorMessage = "Invalid url";
|
||||||
|
private static readonly DefaultBranchName = "master";
|
||||||
|
|
||||||
constructor(props: AddRepoComponentProps) {
|
constructor(props: AddRepoComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -77,7 +78,7 @@ export class AddRepoComponent extends React.Component<AddRepoComponentProps, Add
|
|||||||
});
|
});
|
||||||
let enteredUrl = this.state.textFieldValue;
|
let enteredUrl = this.state.textFieldValue;
|
||||||
if (enteredUrl.indexOf("/tree/") === -1) {
|
if (enteredUrl.indexOf("/tree/") === -1) {
|
||||||
enteredUrl = UrlUtility.createUri(enteredUrl, `tree/`);
|
enteredUrl = UrlUtility.createUri(enteredUrl, `tree/${AddRepoComponent.DefaultBranchName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const repoInfo = GitHubUtils.fromRepoUri(enteredUrl);
|
const repoInfo = GitHubUtils.fromRepoUri(enteredUrl);
|
||||||
@@ -92,7 +93,11 @@ export class AddRepoComponent extends React.Component<AddRepoComponentProps, Add
|
|||||||
const item: RepoListItem = {
|
const item: RepoListItem = {
|
||||||
key: GitHubUtils.toRepoFullName(repo.owner, repo.name),
|
key: GitHubUtils.toRepoFullName(repo.owner, repo.name),
|
||||||
repo,
|
repo,
|
||||||
branches: repoInfo.branch ? [{ name: repoInfo.branch }] : [],
|
branches: [
|
||||||
|
{
|
||||||
|
name: repoInfo.branch,
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
TelemetryProcessor.traceSuccess(
|
TelemetryProcessor.traceSuccess(
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ import { RepoListItem } from "./GitHubReposComponent";
|
|||||||
import {
|
import {
|
||||||
BranchesDropdownCheckboxStyles,
|
BranchesDropdownCheckboxStyles,
|
||||||
BranchesDropdownOptionContainerStyle,
|
BranchesDropdownOptionContainerStyle,
|
||||||
BranchesDropdownStyles,
|
|
||||||
BranchesDropdownWidth,
|
|
||||||
ReposListBranchesColumnWidth,
|
|
||||||
ReposListCheckboxStyles,
|
ReposListCheckboxStyles,
|
||||||
ReposListRepoColumnMinWidth,
|
ReposListRepoColumnMinWidth,
|
||||||
|
ReposListBranchesColumnWidth,
|
||||||
|
BranchesDropdownWidth,
|
||||||
|
BranchesDropdownStyles,
|
||||||
} from "./GitHubStyleConstants";
|
} from "./GitHubStyleConstants";
|
||||||
|
|
||||||
export interface ReposListComponentProps {
|
export interface ReposListComponentProps {
|
||||||
@@ -44,7 +44,6 @@ export interface BranchesProps {
|
|||||||
lastPageInfo?: IGitHubPageInfo;
|
lastPageInfo?: IGitHubPageInfo;
|
||||||
hasMore: boolean;
|
hasMore: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
defaultBranchName: string;
|
|
||||||
loadMore: () => void;
|
loadMore: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +64,7 @@ export class ReposListComponent extends React.Component<ReposListComponentProps>
|
|||||||
private static readonly BranchesColumnName = "Branches";
|
private static readonly BranchesColumnName = "Branches";
|
||||||
private static readonly LoadingText = "Loading...";
|
private static readonly LoadingText = "Loading...";
|
||||||
private static readonly LoadMoreText = "Load more";
|
private static readonly LoadMoreText = "Load more";
|
||||||
private static readonly DefaultBranchNames = "master/main";
|
private static readonly DefaultBranchName = "master";
|
||||||
private static readonly FooterIndex = -1;
|
private static readonly FooterIndex = -1;
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
@@ -156,10 +155,6 @@ export class ReposListComponent extends React.Component<ReposListComponentProps>
|
|||||||
}
|
}
|
||||||
|
|
||||||
const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)];
|
const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)];
|
||||||
if (item.branches.length === 0 && branchesProps.defaultBranchName) {
|
|
||||||
item.branches = [{ name: branchesProps.defaultBranchName }];
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: IDropdownOption[] = branchesProps.branches.map((branch) => ({
|
const options: IDropdownOption[] = branchesProps.branches.map((branch) => ({
|
||||||
key: branch.name,
|
key: branch.name,
|
||||||
text: branch.name,
|
text: branch.name,
|
||||||
@@ -203,7 +198,7 @@ export class ReposListComponent extends React.Component<ReposListComponentProps>
|
|||||||
const dropdownProps: IDropdownProps = {
|
const dropdownProps: IDropdownProps = {
|
||||||
styles: BranchesDropdownStyles,
|
styles: BranchesDropdownStyles,
|
||||||
options: [],
|
options: [],
|
||||||
placeholder: ReposListComponent.DefaultBranchNames,
|
placeholder: ReposListComponent.DefaultBranchName,
|
||||||
disabled: true,
|
disabled: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -277,7 +272,7 @@ export class ReposListComponent extends React.Component<ReposListComponentProps>
|
|||||||
styles: ReposListCheckboxStyles,
|
styles: ReposListCheckboxStyles,
|
||||||
onChange: () => {
|
onChange: () => {
|
||||||
const repoListItem = { ...item };
|
const repoListItem = { ...item };
|
||||||
repoListItem.branches = [];
|
repoListItem.branches = [{ name: ReposListComponent.DefaultBranchName }];
|
||||||
this.props.pinRepo(repoListItem);
|
this.props.pinRepo(repoListItem);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,19 +35,16 @@ const testCassandraAccount: DataModels.DatabaseAccount = {
|
|||||||
const testNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
const testNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||||
authToken: "authToken",
|
authToken: "authToken",
|
||||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com",
|
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com",
|
||||||
forwardingId: "Id",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const testMongoNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
const testMongoNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||||
authToken: "authToken",
|
authToken: "authToken",
|
||||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
|
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
|
||||||
forwardingId: "Id",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const testCassandraNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
const testCassandraNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||||
authToken: "authToken",
|
authToken: "authToken",
|
||||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/cassandra",
|
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/cassandra",
|
||||||
forwardingId: "Id",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("NotebookTerminalComponent", () => {
|
describe("NotebookTerminalComponent", () => {
|
||||||
@@ -55,7 +52,6 @@ describe("NotebookTerminalComponent", () => {
|
|||||||
const props: NotebookTerminalComponentProps = {
|
const props: NotebookTerminalComponentProps = {
|
||||||
databaseAccount: testAccount,
|
databaseAccount: testAccount,
|
||||||
notebookServerInfo: testNotebookServerInfo,
|
notebookServerInfo: testNotebookServerInfo,
|
||||||
tabId: undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||||
@@ -66,7 +62,6 @@ describe("NotebookTerminalComponent", () => {
|
|||||||
const props: NotebookTerminalComponentProps = {
|
const props: NotebookTerminalComponentProps = {
|
||||||
databaseAccount: testMongo32Account,
|
databaseAccount: testMongo32Account,
|
||||||
notebookServerInfo: testMongoNotebookServerInfo,
|
notebookServerInfo: testMongoNotebookServerInfo,
|
||||||
tabId: undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||||
@@ -77,7 +72,6 @@ describe("NotebookTerminalComponent", () => {
|
|||||||
const props: NotebookTerminalComponentProps = {
|
const props: NotebookTerminalComponentProps = {
|
||||||
databaseAccount: testMongo36Account,
|
databaseAccount: testMongo36Account,
|
||||||
notebookServerInfo: testMongoNotebookServerInfo,
|
notebookServerInfo: testMongoNotebookServerInfo,
|
||||||
tabId: undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||||
@@ -88,7 +82,6 @@ describe("NotebookTerminalComponent", () => {
|
|||||||
const props: NotebookTerminalComponentProps = {
|
const props: NotebookTerminalComponentProps = {
|
||||||
databaseAccount: testCassandraAccount,
|
databaseAccount: testCassandraAccount,
|
||||||
notebookServerInfo: testCassandraNotebookServerInfo,
|
notebookServerInfo: testCassandraNotebookServerInfo,
|
||||||
tabId: undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import * as StringUtils from "../../../Utils/StringUtils";
|
|||||||
export interface NotebookTerminalComponentProps {
|
export interface NotebookTerminalComponentProps {
|
||||||
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
||||||
databaseAccount: DataModels.DatabaseAccount;
|
databaseAccount: DataModels.DatabaseAccount;
|
||||||
tabId: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NotebookTerminalComponent extends React.Component<NotebookTerminalComponentProps> {
|
export class NotebookTerminalComponent extends React.Component<NotebookTerminalComponentProps> {
|
||||||
@@ -56,7 +55,6 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
|
|||||||
apiType: userContext.apiType,
|
apiType: userContext.apiType,
|
||||||
authType: userContext.authType,
|
authType: userContext.authType,
|
||||||
databaseAccount: userContext.databaseAccount,
|
databaseAccount: userContext.databaseAccount,
|
||||||
tabId: this.props.tabId,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
postRobot.send(this.terminalWindow, "props", props, {
|
postRobot.send(this.terminalWindow, "props", props, {
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { userContext } from "UserContext";
|
|
||||||
import { HttpStatusCodes } from "../../../Common/Constants";
|
import { HttpStatusCodes } from "../../../Common/Constants";
|
||||||
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
import { IGalleryItem, IJunoResponse, IPublicGalleryData, JunoClient } from "../../../Juno/JunoClient";
|
import { IGalleryItem, IJunoResponse, IPublicGalleryData, JunoClient } from "../../../Juno/JunoClient";
|
||||||
@@ -149,23 +148,18 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
this.traceViewGallery();
|
this.traceViewGallery();
|
||||||
|
|
||||||
const tabs: GalleryTabInfo[] = [];
|
const tabs: GalleryTabInfo[] = [
|
||||||
if (userContext.features.publicGallery) {
|
this.createPublicGalleryTab(
|
||||||
tabs.push(
|
GalleryTab.PublicGallery,
|
||||||
this.createPublicGalleryTab(
|
this.state.publicNotebooks,
|
||||||
GalleryTab.PublicGallery,
|
this.state.isCodeOfConductAccepted
|
||||||
this.state.publicNotebooks,
|
),
|
||||||
this.state.isCodeOfConductAccepted
|
this.createSamplesTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks),
|
||||||
)
|
];
|
||||||
);
|
|
||||||
}
|
|
||||||
tabs.push(this.createSamplesTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks));
|
|
||||||
|
|
||||||
if (this.props.container) {
|
if (this.props.container) {
|
||||||
tabs.push(this.createFavoritesTab(GalleryTab.Favorites, this.state.favoriteNotebooks));
|
tabs.push(this.createFavoritesTab(GalleryTab.Favorites, this.state.favoriteNotebooks));
|
||||||
if (userContext.features.publicGallery) {
|
tabs.push(this.createPublishedNotebooksTab(GalleryTab.Published, this.state.publishedNotebooks));
|
||||||
tabs.push(this.createPublishedNotebooksTab(GalleryTab.Published, this.state.publishedNotebooks));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pivotProps: IPivotProps = {
|
const pivotProps: IPivotProps = {
|
||||||
|
|||||||
@@ -8,6 +8,95 @@ exports[`GalleryViewerComponent renders 1`] = `
|
|||||||
onLinkClick={[Function]}
|
onLinkClick={[Function]}
|
||||||
selectedKey="OfficialSamples"
|
selectedKey="OfficialSamples"
|
||||||
>
|
>
|
||||||
|
<PivotItem
|
||||||
|
headerText="Public gallery"
|
||||||
|
itemKey="PublicGallery"
|
||||||
|
key="PublicGallery"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"marginTop": 20,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="publicGalleryTabContainer"
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
horizontal={true}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 20,
|
||||||
|
"padding": 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wrap={true}
|
||||||
|
>
|
||||||
|
<StackItem
|
||||||
|
grow={true}
|
||||||
|
>
|
||||||
|
<StyledSearchBox
|
||||||
|
onChange={[Function]}
|
||||||
|
placeholder="Search"
|
||||||
|
/>
|
||||||
|
</StackItem>
|
||||||
|
<StackItem>
|
||||||
|
<StyledLabelBase>
|
||||||
|
Sort by
|
||||||
|
</StyledLabelBase>
|
||||||
|
</StackItem>
|
||||||
|
<StackItem
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"minWidth": 200,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Dropdown
|
||||||
|
onChange={[Function]}
|
||||||
|
options={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"key": 0,
|
||||||
|
"text": "Most viewed",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"key": 1,
|
||||||
|
"text": "Most downloaded",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"key": 3,
|
||||||
|
"text": "Most recent",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"key": 2,
|
||||||
|
"text": "Most favorited",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
selectedKey={0}
|
||||||
|
/>
|
||||||
|
</StackItem>
|
||||||
|
<StackItem>
|
||||||
|
<InfoComponent />
|
||||||
|
</StackItem>
|
||||||
|
</Stack>
|
||||||
|
<StackItem>
|
||||||
|
<StyledSpinnerBase
|
||||||
|
size={3}
|
||||||
|
/>
|
||||||
|
</StackItem>
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
</PivotItem>
|
||||||
<PivotItem
|
<PivotItem
|
||||||
headerText="Official samples"
|
headerText="Official samples"
|
||||||
itemKey="OfficialSamples"
|
itemKey="OfficialSamples"
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import Explorer from "../../Explorer";
|
|||||||
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
|
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
|
||||||
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
|
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
|
||||||
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
|
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
|
||||||
|
import { NotebookUtil } from "../../Notebook/NotebookUtil";
|
||||||
import { useNotebook } from "../../Notebook/useNotebook";
|
import { useNotebook } from "../../Notebook/useNotebook";
|
||||||
import { Dialog, TextFieldProps, useDialog } from "../Dialog";
|
import { Dialog, TextFieldProps, useDialog } from "../Dialog";
|
||||||
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
|
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
|
||||||
@@ -52,7 +53,7 @@ export class NotebookViewerComponent
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.clientManager = new NotebookClientV2({
|
this.clientManager = new NotebookClientV2({
|
||||||
connectionInfo: { authToken: undefined, notebookServerEndpoint: undefined, forwardingId: undefined },
|
connectionInfo: { authToken: undefined, notebookServerEndpoint: undefined },
|
||||||
databaseAccountName: undefined,
|
databaseAccountName: undefined,
|
||||||
defaultExperience: "NotebookViewer",
|
defaultExperience: "NotebookViewer",
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
@@ -147,7 +148,9 @@ export class NotebookViewerComponent
|
|||||||
<NotebookMetadataComponent
|
<NotebookMetadataComponent
|
||||||
data={this.state.galleryItem}
|
data={this.state.galleryItem}
|
||||||
isFavorite={this.state.isFavorite}
|
isFavorite={this.state.isFavorite}
|
||||||
downloadButtonText={this.props.container && `Download to ${useNotebook.getState().notebookFolderName}`}
|
downloadButtonText={
|
||||||
|
this.props.container && NotebookUtil.getNotebookBtnTitle(useNotebook.getState().notebookFolderName)
|
||||||
|
}
|
||||||
onTagClick={this.props.onTagClick}
|
onTagClick={this.props.onTagClick}
|
||||||
onFavoriteClick={this.favoriteItem}
|
onFavoriteClick={this.favoriteItem}
|
||||||
onUnfavoriteClick={this.unfavoriteItem}
|
onUnfavoriteClick={this.unfavoriteItem}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "@fluentui/react";
|
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "@fluentui/react";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import DiscardIcon from "../../../../images/discard.svg";
|
import DiscardIcon from "../../../../images/discard.svg";
|
||||||
import SaveIcon from "../../../../images/save-cosmos.svg";
|
import SaveIcon from "../../../../images/save-cosmos.svg";
|
||||||
@@ -72,7 +71,6 @@ export interface SettingsComponentState {
|
|||||||
wasAutopilotOriginallySet: boolean;
|
wasAutopilotOriginallySet: boolean;
|
||||||
isScaleSaveable: boolean;
|
isScaleSaveable: boolean;
|
||||||
isScaleDiscardable: boolean;
|
isScaleDiscardable: boolean;
|
||||||
throughputError: string;
|
|
||||||
|
|
||||||
timeToLive: TtlType;
|
timeToLive: TtlType;
|
||||||
timeToLiveBaseline: TtlType;
|
timeToLiveBaseline: TtlType;
|
||||||
@@ -126,7 +124,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
private changeFeedPolicyVisible: boolean;
|
private changeFeedPolicyVisible: boolean;
|
||||||
private isFixedContainer: boolean;
|
private isFixedContainer: boolean;
|
||||||
private shouldShowIndexingPolicyEditor: boolean;
|
private shouldShowIndexingPolicyEditor: boolean;
|
||||||
private totalThroughputUsed: number;
|
|
||||||
public mongoDBCollectionResource: MongoDBCollectionResource;
|
public mongoDBCollectionResource: MongoDBCollectionResource;
|
||||||
|
|
||||||
constructor(props: SettingsComponentProps) {
|
constructor(props: SettingsComponentProps) {
|
||||||
@@ -149,7 +146,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.offer = this.database?.offer();
|
this.offer = this.database?.offer();
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: SettingsComponentState = {
|
this.state = {
|
||||||
throughput: undefined,
|
throughput: undefined,
|
||||||
throughputBaseline: undefined,
|
throughputBaseline: undefined,
|
||||||
autoPilotThroughput: undefined,
|
autoPilotThroughput: undefined,
|
||||||
@@ -158,7 +155,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
wasAutopilotOriginallySet: false,
|
wasAutopilotOriginallySet: false,
|
||||||
isScaleSaveable: false,
|
isScaleSaveable: false,
|
||||||
isScaleDiscardable: false,
|
isScaleDiscardable: false,
|
||||||
throughputError: undefined,
|
|
||||||
|
|
||||||
timeToLive: undefined,
|
timeToLive: undefined,
|
||||||
timeToLiveBaseline: undefined,
|
timeToLiveBaseline: undefined,
|
||||||
@@ -199,12 +195,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
selectedTab: SettingsV2TabTypes.ScaleTab,
|
selectedTab: SettingsV2TabTypes.ScaleTab,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.state = {
|
|
||||||
...initialState,
|
|
||||||
...this.getBaselineValues(),
|
|
||||||
...this.getAutoscaleBaselineValues(),
|
|
||||||
};
|
|
||||||
|
|
||||||
this.saveSettingsButton = {
|
this.saveSettingsButton = {
|
||||||
isEnabled: this.isSaveSettingsButtonEnabled,
|
isEnabled: this.isSaveSettingsButtonEnabled,
|
||||||
isVisible: () => {
|
isVisible: () => {
|
||||||
@@ -218,11 +208,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
|
||||||
if (throughputCap && throughputCap !== -1) {
|
|
||||||
this.calculateTotalThroughputUsed();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
@@ -231,6 +216,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.loadMongoIndexes();
|
this.loadMongoIndexes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setAutoPilotStates();
|
||||||
this.setBaseline();
|
this.setBaseline();
|
||||||
if (this.props.settingsTab.isActive()) {
|
if (this.props.settingsTab.isActive()) {
|
||||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||||
@@ -268,10 +254,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.throughputError) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
this.state.isScaleSaveable ||
|
this.state.isScaleSaveable ||
|
||||||
this.state.isSubSettingsSaveable ||
|
this.state.isSubSettingsSaveable ||
|
||||||
@@ -291,24 +273,17 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
private getAutoscaleBaselineValues = (): Partial<SettingsComponentState> => {
|
private setAutoPilotStates = (): void => {
|
||||||
const autoscaleMaxThroughput = this.offer?.autoscaleMaxThroughput;
|
const autoscaleMaxThroughput = this.offer?.autoscaleMaxThroughput;
|
||||||
|
|
||||||
if (autoscaleMaxThroughput && AutoPilotUtils.isValidAutoPilotThroughput(autoscaleMaxThroughput)) {
|
if (autoscaleMaxThroughput && AutoPilotUtils.isValidAutoPilotThroughput(autoscaleMaxThroughput)) {
|
||||||
return {
|
this.setState({
|
||||||
isAutoPilotSelected: true,
|
isAutoPilotSelected: true,
|
||||||
wasAutopilotOriginallySet: true,
|
wasAutopilotOriginallySet: true,
|
||||||
autoPilotThroughput: autoscaleMaxThroughput,
|
autoPilotThroughput: autoscaleMaxThroughput,
|
||||||
autoPilotThroughputBaseline: autoscaleMaxThroughput,
|
autoPilotThroughputBaseline: autoscaleMaxThroughput,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
isAutoPilotSelected: false,
|
|
||||||
wasAutopilotOriginallySet: false,
|
|
||||||
autoPilotThroughput: undefined,
|
|
||||||
autoPilotThroughputBaseline: undefined,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public hasProvisioningTypeChanged = (): boolean =>
|
public hasProvisioningTypeChanged = (): boolean =>
|
||||||
@@ -506,26 +481,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
private onMongoIndexingPolicyDiscardableChange = (isMongoIndexingPolicyDiscardable: boolean): void =>
|
private onMongoIndexingPolicyDiscardableChange = (isMongoIndexingPolicyDiscardable: boolean): void =>
|
||||||
this.setState({ isMongoIndexingPolicyDiscardable });
|
this.setState({ isMongoIndexingPolicyDiscardable });
|
||||||
|
|
||||||
private calculateTotalThroughputUsed = (): void => {
|
|
||||||
this.totalThroughputUsed = 0;
|
|
||||||
(useDatabases.getState().databases || []).forEach(async (database) => {
|
|
||||||
if (database.offer()) {
|
|
||||||
const dbThroughput = database.offer().autoscaleMaxThroughput || database.offer().manualThroughput;
|
|
||||||
this.totalThroughputUsed += dbThroughput;
|
|
||||||
}
|
|
||||||
|
|
||||||
(database.collections() || []).forEach(async (collection) => {
|
|
||||||
if (collection.offer()) {
|
|
||||||
const colThroughput = collection.offer().autoscaleMaxThroughput || collection.offer().manualThroughput;
|
|
||||||
this.totalThroughputUsed += colThroughput;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1;
|
|
||||||
this.totalThroughputUsed *= numberOfRegions;
|
|
||||||
};
|
|
||||||
|
|
||||||
public getAnalyticalStorageTtl = (): number => {
|
public getAnalyticalStorageTtl = (): number => {
|
||||||
if (this.isAnalyticalStorageEnabled) {
|
if (this.isAnalyticalStorageEnabled) {
|
||||||
if (this.state.analyticalStorageTtlSelection === TtlType.On) {
|
if (this.state.analyticalStorageTtlSelection === TtlType.On) {
|
||||||
@@ -573,25 +528,21 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
};
|
};
|
||||||
|
|
||||||
public setBaseline = (): void => {
|
public setBaseline = (): void => {
|
||||||
const baselineValues = this.getBaselineValues();
|
|
||||||
const autoscaleBaselineValues = this.getAutoscaleBaselineValues();
|
|
||||||
this.setState({ ...baselineValues, ...autoscaleBaselineValues } as SettingsComponentState);
|
|
||||||
};
|
|
||||||
|
|
||||||
private getBaselineValues = (): Partial<SettingsComponentState> => {
|
|
||||||
const offerThroughput = this.offer?.manualThroughput;
|
const offerThroughput = this.offer?.manualThroughput;
|
||||||
|
|
||||||
if (!this.isCollectionSettingsTab) {
|
if (!this.isCollectionSettingsTab) {
|
||||||
return {
|
this.setState({
|
||||||
throughput: offerThroughput,
|
throughput: offerThroughput,
|
||||||
throughputBaseline: offerThroughput,
|
throughputBaseline: offerThroughput,
|
||||||
};
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultTtl = this.collection.defaultTtl();
|
const defaultTtl = this.collection.defaultTtl();
|
||||||
|
|
||||||
let timeToLive: TtlType;
|
let timeToLive: TtlType = this.state.timeToLive;
|
||||||
let timeToLiveSeconds: number;
|
let timeToLiveSeconds = this.state.timeToLiveSeconds;
|
||||||
switch (defaultTtl) {
|
switch (defaultTtl) {
|
||||||
case undefined:
|
case undefined:
|
||||||
case 0:
|
case 0:
|
||||||
@@ -636,7 +587,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
(this.collection.geospatialConfig && this.collection.geospatialConfig()?.type) || GeospatialConfigType.Geometry;
|
(this.collection.geospatialConfig && this.collection.geospatialConfig()?.type) || GeospatialConfigType.Geometry;
|
||||||
const geoSpatialConfigType = GeospatialConfigType[geospatialConfigTypeString as keyof typeof GeospatialConfigType];
|
const geoSpatialConfigType = GeospatialConfigType[geospatialConfigTypeString as keyof typeof GeospatialConfigType];
|
||||||
|
|
||||||
return {
|
this.setState({
|
||||||
throughput: offerThroughput,
|
throughput: offerThroughput,
|
||||||
throughputBaseline: offerThroughput,
|
throughputBaseline: offerThroughput,
|
||||||
changeFeedPolicy: changeFeedPolicy,
|
changeFeedPolicy: changeFeedPolicy,
|
||||||
@@ -659,7 +610,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
conflictResolutionPolicyProcedureBaseline: conflictResolutionPolicyProcedure,
|
conflictResolutionPolicyProcedureBaseline: conflictResolutionPolicyProcedure,
|
||||||
geospatialConfigType: geoSpatialConfigType,
|
geospatialConfigType: geoSpatialConfigType,
|
||||||
geospatialConfigTypeBaseline: geoSpatialConfigType,
|
geospatialConfigTypeBaseline: geoSpatialConfigType,
|
||||||
};
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private getTabsButtons = (): CommandButtonComponentProps[] => {
|
private getTabsButtons = (): CommandButtonComponentProps[] => {
|
||||||
@@ -692,31 +643,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
return buttons;
|
return buttons;
|
||||||
};
|
};
|
||||||
|
|
||||||
private onMaxAutoPilotThroughputChange = (newThroughput: number): void => {
|
private onMaxAutoPilotThroughputChange = (newThroughput: number): void =>
|
||||||
let throughputError = "";
|
this.setState({ autoPilotThroughput: newThroughput });
|
||||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
|
||||||
const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1;
|
|
||||||
const throughputDelta = (newThroughput - this.offer.autoscaleMaxThroughput) * numberOfRegions;
|
|
||||||
if (throughputCap && throughputCap !== -1 && throughputCap - this.totalThroughputUsed < throughputDelta) {
|
|
||||||
throughputError = `Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${
|
|
||||||
this.totalThroughputUsed + throughputDelta
|
|
||||||
} RU/s. Change total throughput limit in cost management.`;
|
|
||||||
}
|
|
||||||
this.setState({ autoPilotThroughput: newThroughput, throughputError });
|
|
||||||
};
|
|
||||||
|
|
||||||
private onThroughputChange = (newThroughput: number): void => {
|
private onThroughputChange = (newThroughput: number): void => this.setState({ throughput: newThroughput });
|
||||||
let throughputError = "";
|
|
||||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
|
||||||
const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1;
|
|
||||||
const throughputDelta = (newThroughput - this.offer.manualThroughput) * numberOfRegions;
|
|
||||||
if (throughputCap && throughputCap !== -1 && throughputCap - this.totalThroughputUsed < throughputDelta) {
|
|
||||||
throughputError = `Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${
|
|
||||||
this.totalThroughputUsed + throughputDelta
|
|
||||||
} RU/s. Change total throughput limit in cost management.`;
|
|
||||||
}
|
|
||||||
this.setState({ throughput: newThroughput, throughputError });
|
|
||||||
};
|
|
||||||
|
|
||||||
private onAutoPilotSelected = (isAutoPilotSelected: boolean): void =>
|
private onAutoPilotSelected = (isAutoPilotSelected: boolean): void =>
|
||||||
this.setState({ isAutoPilotSelected: isAutoPilotSelected });
|
this.setState({ isAutoPilotSelected: isAutoPilotSelected });
|
||||||
@@ -963,7 +893,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
onScaleSaveableChange: this.onScaleSaveableChange,
|
onScaleSaveableChange: this.onScaleSaveableChange,
|
||||||
onScaleDiscardableChange: this.onScaleDiscardableChange,
|
onScaleDiscardableChange: this.onScaleDiscardableChange,
|
||||||
initialNotification: this.props.settingsTab.pendingNotification(),
|
initialNotification: this.props.settingsTab.pendingNotification(),
|
||||||
throughputError: this.state.throughputError,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!this.isCollectionSettingsTab) {
|
if (!this.isCollectionSettingsTab) {
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ export interface ScaleComponentProps {
|
|||||||
onScaleSaveableChange: (isScaleSaveable: boolean) => void;
|
onScaleSaveableChange: (isScaleSaveable: boolean) => void;
|
||||||
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
|
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
|
||||||
initialNotification: DataModels.Notification;
|
initialNotification: DataModels.Notification;
|
||||||
throughputError?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||||
@@ -190,7 +189,6 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
onScaleDiscardableChange={this.props.onScaleDiscardableChange}
|
onScaleDiscardableChange={this.props.onScaleDiscardableChange}
|
||||||
getThroughputWarningMessage={this.getThroughputWarningMessage}
|
getThroughputWarningMessage={this.getThroughputWarningMessage}
|
||||||
usageSizeInKB={this.props.collection?.usageSizeInKB()}
|
usageSizeInKB={this.props.collection?.usageSizeInKB()}
|
||||||
throughputError={this.props.throughputError}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
|||||||
constructor(props: SubSettingsComponentProps) {
|
constructor(props: SubSettingsComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.geospatialVisible = userContext.apiType === "SQL";
|
this.geospatialVisible = userContext.apiType === "SQL";
|
||||||
|
this.partitionKeyValue = "/" + this.props.collection.partitionKeyProperty;
|
||||||
this.partitionKeyName = userContext.apiType === "Mongo" ? "Shard key" : "Partition key";
|
this.partitionKeyName = userContext.apiType === "Mongo" ? "Shard key" : "Partition key";
|
||||||
this.partitionKeyValue = this.getPartitionKeyValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
@@ -291,14 +291,6 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
private getPartitionKeyValue = (): string => {
|
|
||||||
if (userContext.apiType === "Mongo") {
|
|
||||||
return this.props.collection.partitionKeyProperties?.[0] || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return (this.props.collection.partitionKeyProperties || []).map((property) => "/" + property).join(", ");
|
|
||||||
};
|
|
||||||
|
|
||||||
private getPartitionKeyComponent = (): JSX.Element => (
|
private getPartitionKeyComponent = (): JSX.Element => (
|
||||||
<Stack {...titleAndInputStackProps}>
|
<Stack {...titleAndInputStackProps}>
|
||||||
{this.getPartitionKeyVisible() && (
|
{this.getPartitionKeyVisible() && (
|
||||||
@@ -318,8 +310,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
|||||||
if (
|
if (
|
||||||
userContext.apiType === "Cassandra" ||
|
userContext.apiType === "Cassandra" ||
|
||||||
userContext.apiType === "Tables" ||
|
userContext.apiType === "Tables" ||
|
||||||
!this.props.collection.partitionKeyProperties ||
|
!this.props.collection.partitionKeyProperty ||
|
||||||
this.props.collection.partitionKeyProperties.length === 0 ||
|
|
||||||
(userContext.apiType === "Mongo" && this.props.collection.partitionKey.systemKey)
|
(userContext.apiType === "Mongo" && this.props.collection.partitionKey.systemKey)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { Action, ActionModifiers } from "../../../../../Shared/Telemetry/Telemet
|
|||||||
import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../../../../UserContext";
|
import { userContext } from "../../../../../UserContext";
|
||||||
import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
|
import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
|
||||||
import { autoPilotThroughput1K } from "../../../../../Utils/AutoPilotUtils";
|
import { minAutoPilotThroughput } from "../../../../../Utils/AutoPilotUtils";
|
||||||
import { calculateEstimateNumber, usageInGB } from "../../../../../Utils/PricingUtils";
|
import { calculateEstimateNumber, usageInGB } from "../../../../../Utils/PricingUtils";
|
||||||
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
||||||
import {
|
import {
|
||||||
@@ -75,7 +75,6 @@ export interface ThroughputInputAutoPilotV3Props {
|
|||||||
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
|
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
|
||||||
getThroughputWarningMessage: () => JSX.Element;
|
getThroughputWarningMessage: () => JSX.Element;
|
||||||
usageSizeInKB: number;
|
usageSizeInKB: number;
|
||||||
throughputError?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ThroughputInputAutoPilotV3State {
|
interface ThroughputInputAutoPilotV3State {
|
||||||
@@ -540,8 +539,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
step={AutoPilotUtils.autoPilotIncrementStep}
|
step={AutoPilotUtils.autoPilotIncrementStep}
|
||||||
value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()}
|
value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()}
|
||||||
onChange={this.onAutoPilotThroughputChange}
|
onChange={this.onAutoPilotThroughputChange}
|
||||||
min={autoPilotThroughput1K}
|
min={minAutoPilotThroughput}
|
||||||
errorMessage={this.props.throughputError}
|
|
||||||
/>
|
/>
|
||||||
{!this.overrideWithProvisionedThroughputSettings() && this.getAutoPilotUsageCost()}
|
{!this.overrideWithProvisionedThroughputSettings() && this.getAutoPilotUsageCost()}
|
||||||
{this.minRUperGBSurvey()}
|
{this.minRUperGBSurvey()}
|
||||||
@@ -581,7 +579,6 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
}
|
}
|
||||||
onChange={this.onThroughputChange}
|
onChange={this.onThroughputChange}
|
||||||
min={this.props.minimum}
|
min={this.props.minimum}
|
||||||
errorMessage={this.props.throughputError}
|
|
||||||
/>
|
/>
|
||||||
{this.state.exceedFreeTierThroughput && (
|
{this.state.exceedFreeTierThroughput && (
|
||||||
<MessageBar
|
<MessageBar
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
|||||||
id="autopilotInput"
|
id="autopilotInput"
|
||||||
key="auto pilot throughput input"
|
key="auto pilot throughput input"
|
||||||
label="Max RU/s"
|
label="Max RU/s"
|
||||||
min={1000}
|
min={4000}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
required={true}
|
required={true}
|
||||||
step={1000}
|
step={1000}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const collection = ({
|
|||||||
kind: "hash",
|
kind: "hash",
|
||||||
version: 2,
|
version: 2,
|
||||||
},
|
},
|
||||||
partitionKeyProperties: ["partitionKey"],
|
partitionKeyProperty: "partitionKey",
|
||||||
readSettings: () => {
|
readSettings: () => {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -34,13 +34,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isTabsContentExpanded": [Function],
|
"isTabsContentExpanded": [Function],
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
"onRefreshDatabasesKeyPress": [Function],
|
||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"phoenixClient": PhoenixClient {
|
"phoenixClient": PhoenixClient {},
|
||||||
"retryOptions": Object {
|
|
||||||
"maxTimeout": 5000,
|
|
||||||
"minTimeout": 5000,
|
|
||||||
"retries": 3,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -64,9 +58,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"paths": Array [],
|
"paths": Array [],
|
||||||
"version": 2,
|
"version": 2,
|
||||||
},
|
},
|
||||||
"partitionKeyProperties": Array [
|
"partitionKeyProperty": "partitionKey",
|
||||||
"partitionKey",
|
|
||||||
],
|
|
||||||
"readSettings": [Function],
|
"readSettings": [Function],
|
||||||
"uniqueKeyPolicy": Object {},
|
"uniqueKeyPolicy": Object {},
|
||||||
"usageSizeInKB": [Function],
|
"usageSizeInKB": [Function],
|
||||||
@@ -110,13 +102,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isTabsContentExpanded": [Function],
|
"isTabsContentExpanded": [Function],
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
"onRefreshDatabasesKeyPress": [Function],
|
||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"phoenixClient": PhoenixClient {
|
"phoenixClient": PhoenixClient {},
|
||||||
"retryOptions": Object {
|
|
||||||
"maxTimeout": 5000,
|
|
||||||
"minTimeout": 5000,
|
|
||||||
"retries": 3,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -140,9 +126,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"paths": Array [],
|
"paths": Array [],
|
||||||
"version": 2,
|
"version": 2,
|
||||||
},
|
},
|
||||||
"partitionKeyProperties": Array [
|
"partitionKeyProperty": "partitionKey",
|
||||||
"partitionKey",
|
|
||||||
],
|
|
||||||
"readSettings": [Function],
|
"readSettings": [Function],
|
||||||
"uniqueKeyPolicy": Object {},
|
"uniqueKeyPolicy": Object {},
|
||||||
"usageSizeInKB": [Function],
|
"usageSizeInKB": [Function],
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
const descriptionElement = (
|
const descriptionElement = (
|
||||||
<Stack>
|
<Stack>
|
||||||
{labelElement}
|
{labelElement}
|
||||||
<Text id={`${dataFieldName}-text-display`} aria-labelledby={labelId} style={{ whiteSpace: "pre-line" }}>
|
<Text id={`${dataFieldName}-text-display`} aria-labelledby={labelId}>
|
||||||
{this.props.getTranslation(description.textTKey)}{" "}
|
{this.props.getTranslation(description.textTKey)}{" "}
|
||||||
{description.link && (
|
{description.link && (
|
||||||
<Link target="_blank" href={description.link.href}>
|
<Link target="_blank" href={description.link.href}>
|
||||||
|
|||||||
@@ -27,11 +27,6 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
|||||||
<Text
|
<Text
|
||||||
aria-labelledby="description-label"
|
aria-labelledby="description-label"
|
||||||
id="description-text-display"
|
id="description-text-display"
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"whiteSpace": "pre-line",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
this is an example description text.
|
this is an example description text.
|
||||||
|
|
||||||
@@ -346,11 +341,6 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
|||||||
<Text
|
<Text
|
||||||
aria-labelledby="description-label"
|
aria-labelledby="description-label"
|
||||||
id="description-text-display"
|
id="description-text-display"
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"whiteSpace": "pre-line",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
this is an example description text.
|
this is an example description text.
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,8 @@ const props = {
|
|||||||
isDatabase: false,
|
isDatabase: false,
|
||||||
showFreeTierExceedThroughputTooltip: true,
|
showFreeTierExceedThroughputTooltip: true,
|
||||||
isSharded: true,
|
isSharded: true,
|
||||||
isFreeTier: false,
|
|
||||||
setThroughputValue: () => jest.fn(),
|
setThroughputValue: () => jest.fn(),
|
||||||
setIsAutoscale: () => jest.fn(),
|
setIsAutoscale: () => jest.fn(),
|
||||||
setIsThroughputCapExceeded: () => jest.fn(),
|
|
||||||
onCostAcknowledgeChange: () => jest.fn(),
|
onCostAcknowledgeChange: () => jest.fn(),
|
||||||
};
|
};
|
||||||
describe("ThroughputInput Pane", () => {
|
describe("ThroughputInput Pane", () => {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Checkbox, DirectionalHint, Link, Stack, Text, TextField, TooltipHost } from "@fluentui/react";
|
import { Checkbox, DirectionalHint, Link, Stack, Text, TextField, TooltipHost } from "@fluentui/react";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
||||||
import * as SharedConstants from "../../../Shared/Constants";
|
import * as SharedConstants from "../../../Shared/Constants";
|
||||||
@@ -14,85 +13,28 @@ import "./ThroughputInput.less";
|
|||||||
export interface ThroughputInputProps {
|
export interface ThroughputInputProps {
|
||||||
isDatabase: boolean;
|
isDatabase: boolean;
|
||||||
isSharded: boolean;
|
isSharded: boolean;
|
||||||
isFreeTier: boolean;
|
|
||||||
showFreeTierExceedThroughputTooltip: boolean;
|
showFreeTierExceedThroughputTooltip: boolean;
|
||||||
isQuickstart?: boolean;
|
|
||||||
setThroughputValue: (throughput: number) => void;
|
setThroughputValue: (throughput: number) => void;
|
||||||
setIsAutoscale: (isAutoscale: boolean) => void;
|
setIsAutoscale: (isAutoscale: boolean) => void;
|
||||||
setIsThroughputCapExceeded: (isThroughputCapExceeded: boolean) => void;
|
|
||||||
onCostAcknowledgeChange: (isAcknowledged: boolean) => void;
|
onCostAcknowledgeChange: (isAcknowledged: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||||
isDatabase,
|
isDatabase,
|
||||||
isSharded,
|
|
||||||
isFreeTier,
|
|
||||||
showFreeTierExceedThroughputTooltip,
|
showFreeTierExceedThroughputTooltip,
|
||||||
setThroughputValue,
|
setThroughputValue,
|
||||||
setIsAutoscale,
|
setIsAutoscale,
|
||||||
setIsThroughputCapExceeded,
|
isSharded,
|
||||||
onCostAcknowledgeChange,
|
onCostAcknowledgeChange,
|
||||||
}: ThroughputInputProps) => {
|
}: ThroughputInputProps) => {
|
||||||
const [isAutoscaleSelected, setIsAutoScaleSelected] = useState<boolean>(true);
|
const [isAutoscaleSelected, setIsAutoScaleSelected] = useState<boolean>(true);
|
||||||
const [throughput, setThroughput] = useState<number>(
|
const [throughput, setThroughput] = useState<number>(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
isFreeTier ? AutoPilotUtils.autoPilotThroughput1K : AutoPilotUtils.autoPilotThroughput4K
|
|
||||||
);
|
|
||||||
const [isCostAcknowledged, setIsCostAcknowledged] = useState<boolean>(false);
|
const [isCostAcknowledged, setIsCostAcknowledged] = useState<boolean>(false);
|
||||||
const [throughputError, setThroughputError] = useState<string>("");
|
const [throughputError, setThroughputError] = useState<string>("");
|
||||||
const [totalThroughputUsed, setTotalThroughputUsed] = useState<number>(0);
|
|
||||||
|
|
||||||
setIsAutoscale(isAutoscaleSelected);
|
setIsAutoscale(isAutoscaleSelected);
|
||||||
setThroughputValue(throughput);
|
setThroughputValue(throughput);
|
||||||
|
|
||||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
|
||||||
const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// throughput cap check for the initial state
|
|
||||||
let totalThroughput = 0;
|
|
||||||
(useDatabases.getState().databases || []).forEach((database) => {
|
|
||||||
if (database.offer()) {
|
|
||||||
const dbThroughput = database.offer().autoscaleMaxThroughput || database.offer().manualThroughput;
|
|
||||||
totalThroughput += dbThroughput;
|
|
||||||
}
|
|
||||||
|
|
||||||
(database.collections() || []).forEach((collection) => {
|
|
||||||
if (collection.offer()) {
|
|
||||||
const colThroughput = collection.offer().autoscaleMaxThroughput || collection.offer().manualThroughput;
|
|
||||||
totalThroughput += colThroughput;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
totalThroughput *= numberOfRegions;
|
|
||||||
setTotalThroughputUsed(totalThroughput);
|
|
||||||
|
|
||||||
if (throughputCap && throughputCap !== -1 && throughputCap - totalThroughput < throughput) {
|
|
||||||
setThroughputError(
|
|
||||||
`Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${
|
|
||||||
totalThroughput + throughput * numberOfRegions
|
|
||||||
} RU/s. Change total throughput limit in cost management.`
|
|
||||||
);
|
|
||||||
|
|
||||||
setIsThroughputCapExceeded(true);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const checkThroughputCap = (newThroughput: number): boolean => {
|
|
||||||
if (throughputCap && throughputCap !== -1 && throughputCap - totalThroughputUsed < newThroughput) {
|
|
||||||
setThroughputError(
|
|
||||||
`Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${
|
|
||||||
totalThroughputUsed + newThroughput * numberOfRegions
|
|
||||||
} RU/s. Change total throughput limit in cost management.`
|
|
||||||
);
|
|
||||||
setIsThroughputCapExceeded(true);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
setThroughputError("");
|
|
||||||
setIsThroughputCapExceeded(false);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getThroughputLabelText = (): string => {
|
const getThroughputLabelText = (): string => {
|
||||||
let throughputHeaderText: string;
|
let throughputHeaderText: string;
|
||||||
if (isAutoscaleSelected) {
|
if (isAutoscaleSelected) {
|
||||||
@@ -118,17 +60,11 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
const newThroughput = parseInt(newInput);
|
const newThroughput = parseInt(newInput);
|
||||||
setThroughput(newThroughput);
|
setThroughput(newThroughput);
|
||||||
setThroughputValue(newThroughput);
|
setThroughputValue(newThroughput);
|
||||||
|
|
||||||
if (!isSharded && newThroughput > 10000) {
|
if (!isSharded && newThroughput > 10000) {
|
||||||
setThroughputError("Unsharded collections support up to 10,000 RUs");
|
setThroughputError("Unsharded collections support up to 10,000 RUs");
|
||||||
return;
|
} else {
|
||||||
|
setThroughputError("");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkThroughputCap(newThroughput)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setThroughputError("");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAutoScaleTooltip = (): string => {
|
const getAutoScaleTooltip = (): string => {
|
||||||
@@ -156,20 +92,15 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
|
|
||||||
const handleOnChangeMode = (event: React.ChangeEvent<HTMLInputElement>, mode: string): void => {
|
const handleOnChangeMode = (event: React.ChangeEvent<HTMLInputElement>, mode: string): void => {
|
||||||
if (mode === "Autoscale") {
|
if (mode === "Autoscale") {
|
||||||
const defaultThroughput = isFreeTier
|
setThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
? AutoPilotUtils.autoPilotThroughput1K
|
|
||||||
: AutoPilotUtils.autoPilotThroughput4K;
|
|
||||||
setThroughput(defaultThroughput);
|
|
||||||
setIsAutoScaleSelected(true);
|
setIsAutoScaleSelected(true);
|
||||||
setThroughputValue(defaultThroughput);
|
setThroughputValue(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
setIsAutoscale(true);
|
setIsAutoscale(true);
|
||||||
checkThroughputCap(defaultThroughput);
|
|
||||||
} else {
|
} else {
|
||||||
setThroughput(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
setThroughput(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
||||||
setIsAutoScaleSelected(false);
|
setIsAutoScaleSelected(false);
|
||||||
setThroughputValue(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
setThroughputValue(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
||||||
setIsAutoscale(false);
|
setIsAutoscale(false);
|
||||||
checkThroughputCap(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -227,7 +158,6 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
id="autoscaleRUValueField"
|
|
||||||
type="number"
|
type="number"
|
||||||
styles={{
|
styles={{
|
||||||
fieldGroup: { width: 300, height: 27 },
|
fieldGroup: { width: 300, height: 27 },
|
||||||
@@ -235,7 +165,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
}}
|
}}
|
||||||
onChange={(event, newInput?: string) => onThroughputValueChange(newInput)}
|
onChange={(event, newInput?: string) => onThroughputValueChange(newInput)}
|
||||||
step={AutoPilotUtils.autoPilotIncrementStep}
|
step={AutoPilotUtils.autoPilotIncrementStep}
|
||||||
min={AutoPilotUtils.autoPilotThroughput1K}
|
min={AutoPilotUtils.minAutoPilotThroughput}
|
||||||
value={throughput.toString()}
|
value={throughput.toString()}
|
||||||
aria-label="Max request units per second"
|
aria-label="Max request units per second"
|
||||||
required={true}
|
required={true}
|
||||||
|
|||||||
@@ -3,11 +3,9 @@
|
|||||||
exports[`ThroughputInput Pane should render Default properly 1`] = `
|
exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
isDatabase={false}
|
isDatabase={false}
|
||||||
isFreeTier={false}
|
|
||||||
isSharded={true}
|
isSharded={true}
|
||||||
onCostAcknowledgeChange={[Function]}
|
onCostAcknowledgeChange={[Function]}
|
||||||
setIsAutoscale={[Function]}
|
setIsAutoscale={[Function]}
|
||||||
setIsThroughputCapExceeded={[Function]}
|
|
||||||
setThroughputValue={[Function]}
|
setThroughputValue={[Function]}
|
||||||
showFreeTierExceedThroughputTooltip={true}
|
showFreeTierExceedThroughputTooltip={true}
|
||||||
>
|
>
|
||||||
@@ -1637,9 +1635,8 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
aria-label="Max request units per second"
|
aria-label="Max request units per second"
|
||||||
errorMessage=""
|
errorMessage=""
|
||||||
id="autoscaleRUValueField"
|
|
||||||
key=".0:$.2"
|
key=".0:$.2"
|
||||||
min={1000}
|
min={4000}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
required={true}
|
required={true}
|
||||||
step={1000}
|
step={1000}
|
||||||
@@ -1661,8 +1658,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
aria-label="Max request units per second"
|
aria-label="Max request units per second"
|
||||||
deferredValidationTime={200}
|
deferredValidationTime={200}
|
||||||
errorMessage=""
|
errorMessage=""
|
||||||
id="autoscaleRUValueField"
|
min={4000}
|
||||||
min={1000}
|
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
required={true}
|
required={true}
|
||||||
resizable={true}
|
resizable={true}
|
||||||
@@ -1957,8 +1953,8 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
<input
|
<input
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
className="ms-TextField-field field-64"
|
className="ms-TextField-field field-64"
|
||||||
id="autoscaleRUValueField"
|
id="TextField2"
|
||||||
min={1000}
|
min={4000}
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onFocus={[Function]}
|
onFocus={[Function]}
|
||||||
|
|||||||
@@ -173,7 +173,6 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
|||||||
onClick={(event: React.MouseEvent<HTMLDivElement>) => this.onNodeClick(event, node)}
|
onClick={(event: React.MouseEvent<HTMLDivElement>) => this.onNodeClick(event, node)}
|
||||||
onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) => this.onNodeKeyPress(event, node)}
|
onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) => this.onNodeKeyPress(event, node)}
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
id={node.id}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`treeNodeHeader ${this.state.isMenuShowing ? "showingMenu" : ""}`}
|
className={`treeNodeHeader ${this.state.isMenuShowing ? "showingMenu" : ""}`}
|
||||||
|
|||||||
@@ -137,7 +137,6 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
|
|||||||
exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`] = `
|
exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`] = `
|
||||||
<div
|
<div
|
||||||
className="nodeClassname main12 nodeItem "
|
className="nodeClassname main12 nodeItem "
|
||||||
id="id"
|
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyPress={[Function]}
|
onKeyPress={[Function]}
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
@@ -360,7 +359,6 @@ exports[`TreeNodeComponent renders loading icon 1`] = `
|
|||||||
exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents separated 1`] = `
|
exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents separated 1`] = `
|
||||||
<div
|
<div
|
||||||
className="nodeClassname main12 nodeItem "
|
className="nodeClassname main12 nodeItem "
|
||||||
id="id"
|
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyPress={[Function]}
|
onKeyPress={[Function]}
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
|
|||||||
@@ -68,10 +68,11 @@ export class ContainerSampleGenerator {
|
|||||||
return database.findCollectionWithId(this.sampleDataFile.collectionId);
|
return database.findCollectionWithId(this.sampleDataFile.collectionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async populateContainerAsync(collection: ViewModels.Collection): Promise<void> {
|
private async populateContainerAsync(collection: ViewModels.Collection): Promise<void> {
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
throw new Error("No container to populate");
|
throw new Error("No container to populate");
|
||||||
}
|
}
|
||||||
|
const promises: Q.Promise<any>[] = [];
|
||||||
|
|
||||||
if (userContext.apiType === "Gremlin") {
|
if (userContext.apiType === "Gremlin") {
|
||||||
// For Gremlin, all queries are executed sequentially, because some queries might be dependent on other queries
|
// For Gremlin, all queries are executed sequentially, because some queries might be dependent on other queries
|
||||||
|
|||||||
@@ -1,31 +1,24 @@
|
|||||||
import { Link } from "@fluentui/react/lib/Link";
|
import { Link } from "@fluentui/react/lib/Link";
|
||||||
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
|
||||||
import { IGalleryItem } from "Juno/JunoClient";
|
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import _ from "underscore";
|
import _ from "underscore";
|
||||||
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointValidation";
|
|
||||||
import shallow from "zustand/shallow";
|
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
||||||
import * as Constants from "../Common/Constants";
|
import * as Constants from "../Common/Constants";
|
||||||
import { Areas, ConnectionStatusType, HttpStatusCodes, Notebook, PoolIdType } from "../Common/Constants";
|
import { ConnectionStatusType, HttpStatusCodes, Notebook } from "../Common/Constants";
|
||||||
import { readCollection } from "../Common/dataAccess/readCollection";
|
import { readCollection } from "../Common/dataAccess/readCollection";
|
||||||
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
||||||
|
import { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility";
|
||||||
import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils";
|
||||||
import * as Logger from "../Common/Logger";
|
import * as Logger from "../Common/Logger";
|
||||||
import { QueriesClient } from "../Common/QueriesClient";
|
import { QueriesClient } from "../Common/QueriesClient";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import {
|
import { ContainerConnectionInfo } from "../Contracts/DataModels";
|
||||||
ContainerConnectionInfo,
|
|
||||||
IPhoenixConnectionInfoResult,
|
|
||||||
IProvisionData,
|
|
||||||
IResponse,
|
|
||||||
} from "../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
||||||
import { useSidePanel } from "../hooks/useSidePanel";
|
import { useSidePanel } from "../hooks/useSidePanel";
|
||||||
import { useTabs } from "../hooks/useTabs";
|
import { useTabs } from "../hooks/useTabs";
|
||||||
|
import { IGalleryItem } from "../Juno/JunoClient";
|
||||||
import { PhoenixClient } from "../Phoenix/PhoenixClient";
|
import { PhoenixClient } from "../Phoenix/PhoenixClient";
|
||||||
import * as ExplorerSettings from "../Shared/ExplorerSettings";
|
import * as ExplorerSettings from "../Shared/ExplorerSettings";
|
||||||
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
||||||
@@ -33,7 +26,12 @@ import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
|||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { getCollectionName, getUploadName } from "../Utils/APITypeUtils";
|
import { getCollectionName, getUploadName } from "../Utils/APITypeUtils";
|
||||||
import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
|
import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
|
||||||
import { listByDatabaseAccount } from "../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
import {
|
||||||
|
get as getWorkspace,
|
||||||
|
listByDatabaseAccount,
|
||||||
|
listConnectionInfo,
|
||||||
|
start,
|
||||||
|
} from "../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
||||||
import { stringToBlob } from "../Utils/BlobUtils";
|
import { stringToBlob } from "../Utils/BlobUtils";
|
||||||
import { isCapabilityEnabled } from "../Utils/CapabilityUtils";
|
import { isCapabilityEnabled } from "../Utils/CapabilityUtils";
|
||||||
import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
|
import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
|
||||||
@@ -47,12 +45,13 @@ import * as FileSystemUtil from "./Notebook/FileSystemUtil";
|
|||||||
import { SnapshotRequest } from "./Notebook/NotebookComponent/types";
|
import { SnapshotRequest } from "./Notebook/NotebookComponent/types";
|
||||||
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
|
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
|
||||||
import type NotebookManager from "./Notebook/NotebookManager";
|
import type NotebookManager from "./Notebook/NotebookManager";
|
||||||
import { NotebookPaneContent } from "./Notebook/NotebookManager";
|
import type { NotebookPaneContent } from "./Notebook/NotebookManager";
|
||||||
import { NotebookUtil } from "./Notebook/NotebookUtil";
|
import { NotebookUtil } from "./Notebook/NotebookUtil";
|
||||||
import { useNotebook } from "./Notebook/useNotebook";
|
import { useNotebook } from "./Notebook/useNotebook";
|
||||||
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
|
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
|
||||||
import { CassandraAddCollectionPane } from "./Panes/CassandraAddCollectionPane/CassandraAddCollectionPane";
|
import { CassandraAddCollectionPane } from "./Panes/CassandraAddCollectionPane/CassandraAddCollectionPane";
|
||||||
import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane";
|
import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane";
|
||||||
|
import { SetupNoteBooksPanel } from "./Panes/SetupNotebooksPanel/SetupNotebooksPanel";
|
||||||
import { StringInputPane } from "./Panes/StringInputPane/StringInputPane";
|
import { StringInputPane } from "./Panes/StringInputPane/StringInputPane";
|
||||||
import { UploadFilePane } from "./Panes/UploadFilePane/UploadFilePane";
|
import { UploadFilePane } from "./Panes/UploadFilePane/UploadFilePane";
|
||||||
import { UploadItemsPane } from "./Panes/UploadItemsPane/UploadItemsPane";
|
import { UploadItemsPane } from "./Panes/UploadItemsPane/UploadItemsPane";
|
||||||
@@ -166,23 +165,20 @@ export default class Explorer {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useNotebook.subscribe(
|
useNotebook.subscribe(
|
||||||
async () => this.initiateAndRefreshNotebookList(),
|
async () => {
|
||||||
(state) => [state.isNotebookEnabled, state.isRefreshed],
|
this.initiateAndRefreshNotebookList();
|
||||||
shallow
|
useNotebook.getState().setIsRefreshed(false);
|
||||||
|
},
|
||||||
|
(state) => state.isNotebookEnabled || state.isRefreshed
|
||||||
);
|
);
|
||||||
|
|
||||||
this.resourceTree = new ResourceTreeAdapter(this);
|
this.resourceTree = new ResourceTreeAdapter(this);
|
||||||
|
|
||||||
// Override notebook server parameters from URL parameters
|
// Override notebook server parameters from URL parameters
|
||||||
if (
|
if (userContext.features.notebookServerUrl && userContext.features.notebookServerToken) {
|
||||||
userContext.features.notebookServerUrl &&
|
|
||||||
validateEndpoint(userContext.features.notebookServerUrl, allowedNotebookServerUrls) &&
|
|
||||||
userContext.features.notebookServerToken
|
|
||||||
) {
|
|
||||||
useNotebook.getState().setNotebookServerInfo({
|
useNotebook.getState().setNotebookServerInfo({
|
||||||
notebookServerEndpoint: userContext.features.notebookServerUrl,
|
notebookServerEndpoint: userContext.features.notebookServerUrl,
|
||||||
authToken: userContext.features.notebookServerToken,
|
authToken: userContext.features.notebookServerToken,
|
||||||
forwardingId: undefined,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,6 +186,19 @@ export default class Explorer {
|
|||||||
useNotebook.getState().setNotebookBasePath(userContext.features.notebookBasePath);
|
useNotebook.getState().setNotebookBasePath(userContext.features.notebookBasePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userContext.features.livyEndpoint) {
|
||||||
|
useNotebook.getState().setSparkClusterConnectionInfo({
|
||||||
|
userName: undefined,
|
||||||
|
password: undefined,
|
||||||
|
endpoints: [
|
||||||
|
{
|
||||||
|
endpoint: userContext.features.livyEndpoint,
|
||||||
|
kind: DataModels.SparkClusterEndpointKind.Livy,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.refreshExplorer();
|
this.refreshExplorer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,7 +307,7 @@ export default class Explorer {
|
|||||||
db1.id().localeCompare(db2.id())
|
db1.id().localeCompare(db2.id())
|
||||||
);
|
);
|
||||||
useDatabases.setState({ databases: updatedDatabases });
|
useDatabases.setState({ databases: updatedDatabases });
|
||||||
await this.refreshAndExpandNewDatabases(deltaDatabases.toAdd, updatedDatabases);
|
await this.refreshAndExpandNewDatabases(deltaDatabases.toAdd, currentDatabases);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = getErrorMessage(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
TelemetryProcessor.traceFailure(
|
TelemetryProcessor.traceFailure(
|
||||||
@@ -343,94 +352,74 @@ export default class Explorer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._isInitializingNotebooks = true;
|
this._isInitializingNotebooks = true;
|
||||||
|
if (userContext.features.phoenix === false) {
|
||||||
|
await this.ensureNotebookWorkspaceRunning();
|
||||||
|
const connectionInfo = await listConnectionInfo(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
databaseAccount.name,
|
||||||
|
"default"
|
||||||
|
);
|
||||||
|
|
||||||
|
useNotebook.getState().setNotebookServerInfo({
|
||||||
|
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.notebookServerEndpoint,
|
||||||
|
authToken: userContext.features.notebookServerToken || connectionInfo.authToken,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.refreshNotebookList();
|
this.refreshNotebookList();
|
||||||
|
|
||||||
this._isInitializingNotebooks = false;
|
this._isInitializingNotebooks = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async allocateContainer(): Promise<void> {
|
public async allocateContainer(): Promise<void> {
|
||||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||||
const isAllocating = useNotebook.getState().isAllocating;
|
const isAllocating = useNotebook.getState().isAllocating;
|
||||||
if (
|
if (isAllocating === false && notebookServerInfo && notebookServerInfo.notebookServerEndpoint === undefined) {
|
||||||
isAllocating === false &&
|
const provisionData = {
|
||||||
(notebookServerInfo === undefined ||
|
aadToken: userContext.authorizationToken,
|
||||||
(notebookServerInfo && notebookServerInfo.notebookServerEndpoint === undefined))
|
subscriptionId: userContext.subscriptionId,
|
||||||
) {
|
resourceGroup: userContext.resourceGroup,
|
||||||
const provisionData: IProvisionData = {
|
dbAccountName: userContext.databaseAccount.name,
|
||||||
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
|
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
|
||||||
poolId: PoolIdType.DefaultPoolId,
|
|
||||||
};
|
};
|
||||||
const connectionStatus: ContainerConnectionInfo = {
|
const connectionStatus: ContainerConnectionInfo = {
|
||||||
status: ConnectionStatusType.Connecting,
|
status: ConnectionStatusType.Connecting,
|
||||||
};
|
};
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||||
let connectionInfo;
|
|
||||||
try {
|
try {
|
||||||
TelemetryProcessor.traceStart(Action.PhoenixConnection, {
|
|
||||||
dataExplorerArea: Areas.Notebook,
|
|
||||||
});
|
|
||||||
useNotebook.getState().setIsAllocating(true);
|
useNotebook.getState().setIsAllocating(true);
|
||||||
connectionInfo = await this.phoenixClient.allocateContainer(provisionData);
|
const connectionInfo = await this.phoenixClient.containerConnectionInfo(provisionData);
|
||||||
if (!connectionInfo?.data?.phoenixServiceUrl) {
|
if (
|
||||||
throw new Error(`PhoenixServiceUrl is invalid!`);
|
connectionInfo.status === HttpStatusCodes.OK &&
|
||||||
}
|
connectionInfo.data &&
|
||||||
await this.setNotebookInfo(connectionInfo, connectionStatus);
|
connectionInfo.data.notebookServerUrl
|
||||||
TelemetryProcessor.traceSuccess(Action.PhoenixConnection, {
|
) {
|
||||||
dataExplorerArea: Areas.Notebook,
|
connectionStatus.status = ConnectionStatusType.Connected;
|
||||||
});
|
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||||
} catch (error) {
|
useNotebook.getState().setNotebookServerInfo({
|
||||||
TelemetryProcessor.traceFailure(Action.PhoenixConnection, {
|
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.data.notebookServerUrl,
|
||||||
dataExplorerArea: Areas.Notebook,
|
authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken,
|
||||||
status: error.status,
|
});
|
||||||
error: getErrorMessage(error),
|
this.notebookManager?.notebookClient
|
||||||
errorStack: getErrorStack(error),
|
.getMemoryUsage()
|
||||||
});
|
.then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo));
|
||||||
connectionStatus.status = ConnectionStatusType.Failed;
|
useNotebook.getState().setIsAllocating(false);
|
||||||
useNotebook.getState().resetContainerConnection(connectionStatus);
|
|
||||||
if (error?.status === HttpStatusCodes.Forbidden && error.message) {
|
|
||||||
useDialog.getState().showOkModalDialog("Connection Failed", `${error.message}`);
|
|
||||||
} else {
|
} else {
|
||||||
useDialog
|
connectionStatus.status = ConnectionStatusType.Failed;
|
||||||
.getState()
|
useNotebook.getState().resetConatinerConnection(connectionStatus);
|
||||||
.showOkModalDialog(
|
|
||||||
"Connection Failed",
|
|
||||||
"We are unable to connect to the temporary workspace. Please try again in a few minutes. If the error persists, file a support ticket."
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
connectionStatus.status = ConnectionStatusType.Failed;
|
||||||
|
useNotebook.getState().resetConatinerConnection(connectionStatus);
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
|
||||||
useNotebook.getState().setIsAllocating(false);
|
|
||||||
this.refreshCommandBarButtons();
|
|
||||||
this.refreshNotebookList();
|
|
||||||
this._isInitializingNotebooks = false;
|
|
||||||
}
|
}
|
||||||
|
this.refreshNotebookList();
|
||||||
|
|
||||||
|
this._isInitializingNotebooks = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async setNotebookInfo(
|
|
||||||
connectionInfo: IResponse<IPhoenixConnectionInfoResult>,
|
|
||||||
connectionStatus: DataModels.ContainerConnectionInfo
|
|
||||||
) {
|
|
||||||
const containerData = {
|
|
||||||
forwardingId: connectionInfo.data.forwardingId,
|
|
||||||
dbAccountName: userContext.databaseAccount.name,
|
|
||||||
};
|
|
||||||
await this.phoenixClient.initiateContainerHeartBeat(containerData);
|
|
||||||
|
|
||||||
connectionStatus.status = ConnectionStatusType.Connected;
|
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
|
||||||
useNotebook.getState().setNotebookServerInfo({
|
|
||||||
notebookServerEndpoint:
|
|
||||||
(validateEndpoint(userContext.features.notebookServerUrl, allowedNotebookServerUrls) &&
|
|
||||||
userContext.features.notebookServerUrl) ||
|
|
||||||
connectionInfo.data.phoenixServiceUrl,
|
|
||||||
authToken: userContext.features.notebookServerToken || connectionInfo.data.authToken,
|
|
||||||
forwardingId: connectionInfo.data.forwardingId,
|
|
||||||
});
|
|
||||||
this.notebookManager?.notebookClient
|
|
||||||
.getMemoryUsage()
|
|
||||||
.then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
public resetNotebookWorkspace(): void {
|
public resetNotebookWorkspace(): void {
|
||||||
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookClient) {
|
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookClient) {
|
||||||
handleError(
|
handleError(
|
||||||
@@ -439,14 +428,11 @@ export default class Explorer {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const dialogContent = useNotebook.getState().isPhoenixNotebooks
|
|
||||||
? "Notebooks saved in the temporary workspace will be deleted. Do you want to proceed?"
|
|
||||||
: "This lets you keep your notebook files and the workspace will be restored to default. Proceed anyway?";
|
|
||||||
|
|
||||||
const resetConfirmationDialogProps: DialogProps = {
|
const resetConfirmationDialogProps: DialogProps = {
|
||||||
isModal: true,
|
isModal: true,
|
||||||
title: "Reset Workspace",
|
title: "Reset Workspace",
|
||||||
subText: dialogContent,
|
subText: "This lets you keep your notebook files and the workspace will be restored to default. Proceed anyway?",
|
||||||
primaryButtonText: "OK",
|
primaryButtonText: "OK",
|
||||||
secondaryButtonText: "Cancel",
|
secondaryButtonText: "Cancel",
|
||||||
onPrimaryButtonClick: this._resetNotebookWorkspace,
|
onPrimaryButtonClick: this._resetNotebookWorkspace,
|
||||||
@@ -472,57 +458,48 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async ensureNotebookWorkspaceRunning() {
|
||||||
|
if (!userContext.databaseAccount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clearMessage;
|
||||||
|
try {
|
||||||
|
const notebookWorkspace = await getWorkspace(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
"default"
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
notebookWorkspace &&
|
||||||
|
notebookWorkspace.properties &&
|
||||||
|
notebookWorkspace.properties.status &&
|
||||||
|
notebookWorkspace.properties.status.toLowerCase() === "stopped"
|
||||||
|
) {
|
||||||
|
clearMessage = NotificationConsoleUtils.logConsoleProgress("Initializing notebook workspace");
|
||||||
|
await start(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, "default");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "Explorer/ensureNotebookWorkspaceRunning", "Failed to initialize notebook workspace");
|
||||||
|
} finally {
|
||||||
|
clearMessage && clearMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _resetNotebookWorkspace = async () => {
|
private _resetNotebookWorkspace = async () => {
|
||||||
useDialog.getState().closeDialog();
|
useDialog.getState().closeDialog();
|
||||||
const clearInProgressMessage = logConsoleProgress("Resetting notebook workspace");
|
const clearInProgressMessage = logConsoleProgress("Resetting notebook workspace");
|
||||||
let connectionStatus: ContainerConnectionInfo;
|
|
||||||
try {
|
try {
|
||||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
await this.notebookManager?.notebookClient.resetWorkspace();
|
||||||
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
|
||||||
const error = "No server endpoint detected";
|
|
||||||
Logger.logError(error, "NotebookContainerClient/resetWorkspace");
|
|
||||||
logConsoleError(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TelemetryProcessor.traceStart(Action.PhoenixResetWorkspace, {
|
|
||||||
dataExplorerArea: Areas.Notebook,
|
|
||||||
});
|
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
|
||||||
useTabs.getState().closeAllNotebookTabs(true);
|
|
||||||
connectionStatus = {
|
|
||||||
status: ConnectionStatusType.Connecting,
|
|
||||||
};
|
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
|
||||||
}
|
|
||||||
const connectionInfo = await this.notebookManager?.notebookClient.resetWorkspace();
|
|
||||||
if (connectionInfo?.status !== HttpStatusCodes.OK) {
|
|
||||||
throw new Error(`Reset Workspace: Received status code- ${connectionInfo?.status}`);
|
|
||||||
}
|
|
||||||
if (!connectionInfo?.data?.phoenixServiceUrl) {
|
|
||||||
throw new Error(`Reset Workspace: PhoenixServiceUrl is invalid!`);
|
|
||||||
}
|
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
|
||||||
await this.setNotebookInfo(connectionInfo, connectionStatus);
|
|
||||||
useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
|
|
||||||
}
|
|
||||||
logConsoleInfo("Successfully reset notebook workspace");
|
logConsoleInfo("Successfully reset notebook workspace");
|
||||||
TelemetryProcessor.traceSuccess(Action.PhoenixResetWorkspace, {
|
TelemetryProcessor.traceSuccess(Action.ResetNotebookWorkspace);
|
||||||
dataExplorerArea: Areas.Notebook,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Failed to reset notebook workspace: ${error}`);
|
logConsoleError(`Failed to reset notebook workspace: ${error}`);
|
||||||
TelemetryProcessor.traceFailure(Action.PhoenixResetWorkspace, {
|
TelemetryProcessor.traceFailure(Action.ResetNotebookWorkspace, {
|
||||||
dataExplorerArea: Areas.Notebook,
|
|
||||||
error: getErrorMessage(error),
|
error: getErrorMessage(error),
|
||||||
errorStack: getErrorStack(error),
|
errorStack: getErrorStack(error),
|
||||||
});
|
});
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
|
||||||
connectionStatus = {
|
|
||||||
status: ConnectionStatusType.Failed,
|
|
||||||
};
|
|
||||||
useNotebook.getState().resetContainerConnection(connectionStatus);
|
|
||||||
useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
|
|
||||||
}
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearInProgressMessage();
|
clearInProgressMessage();
|
||||||
@@ -714,8 +691,8 @@ export default class Explorer {
|
|||||||
if (!notebookContentItem || !notebookContentItem.path) {
|
if (!notebookContentItem || !notebookContentItem.path) {
|
||||||
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
|
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
|
||||||
}
|
}
|
||||||
if (notebookContentItem.type === NotebookContentItemType.Notebook && useNotebook.getState().isPhoenixNotebooks) {
|
if (notebookContentItem.type === NotebookContentItemType.Notebook && NotebookUtil.isPhoenixEnabled()) {
|
||||||
await this.allocateContainer();
|
this.allocateContainer();
|
||||||
}
|
}
|
||||||
|
|
||||||
const notebookTabs = useTabs
|
const notebookTabs = useTabs
|
||||||
@@ -932,17 +909,20 @@ export default class Explorer {
|
|||||||
/**
|
/**
|
||||||
* This creates a new notebook file, then opens the notebook
|
* This creates a new notebook file, then opens the notebook
|
||||||
*/
|
*/
|
||||||
public async onNewNotebookClicked(parent?: NotebookContentItem, isGithubTree?: boolean): Promise<void> {
|
public onNewNotebookClicked(parent?: NotebookContentItem, isGithubTree?: boolean): void {
|
||||||
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) {
|
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) {
|
||||||
const error = "Attempt to create new notebook, but notebook is not enabled";
|
const error = "Attempt to create new notebook, but notebook is not enabled";
|
||||||
handleError(error, "Explorer/onNewNotebookClicked");
|
handleError(error, "Explorer/onNewNotebookClicked");
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
const isPhoenixEnabled = NotebookUtil.isPhoenixEnabled();
|
||||||
|
if (isPhoenixEnabled) {
|
||||||
if (isGithubTree) {
|
if (isGithubTree) {
|
||||||
await this.allocateContainer();
|
async () => {
|
||||||
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
await this.allocateContainer();
|
||||||
this.createNewNoteBook(parent, isGithubTree);
|
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||||
|
this.createNewNoteBook(parent, isGithubTree);
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
useDialog.getState().showOkCancelModalDialog(
|
useDialog.getState().showOkCancelModalDialog(
|
||||||
Notebook.newNotebookModalTitle,
|
Notebook.newNotebookModalTitle,
|
||||||
@@ -1027,7 +1007,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async openNotebookTerminal(kind: ViewModels.TerminalKind): Promise<void> {
|
public async openNotebookTerminal(kind: ViewModels.TerminalKind): Promise<void> {
|
||||||
if (useNotebook.getState().isPhoenixFeatures) {
|
if (NotebookUtil.isPhoenixEnabled()) {
|
||||||
await this.allocateContainer();
|
await this.allocateContainer();
|
||||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||||
if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) {
|
if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) {
|
||||||
@@ -1036,8 +1016,8 @@ export default class Explorer {
|
|||||||
useDialog
|
useDialog
|
||||||
.getState()
|
.getState()
|
||||||
.showOkModalDialog(
|
.showOkModalDialog(
|
||||||
"Failed to connect",
|
"Failed to Connect",
|
||||||
"Failed to connect to temporary workspace. This could happen because of network issues. Please refresh the page and try again."
|
"Failed to connect temporary workspace, this could happen because of network issue please refresh and try again."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1067,7 +1047,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
const terminalTabs: TerminalTab[] = useTabs
|
const terminalTabs: TerminalTab[] = useTabs
|
||||||
.getState()
|
.getState()
|
||||||
.getTabs(ViewModels.CollectionTabKind.Terminal, (tab) => tab.tabTitle().startsWith(title)) as TerminalTab[];
|
.getTabs(ViewModels.CollectionTabKind.Terminal, (tab) => tab.tabTitle() === title) as TerminalTab[];
|
||||||
|
|
||||||
let index = 1;
|
let index = 1;
|
||||||
if (terminalTabs.length > 0) {
|
if (terminalTabs.length > 0) {
|
||||||
@@ -1130,12 +1110,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onNewCollectionClicked(
|
public async onNewCollectionClicked(databaseId?: string): Promise<void> {
|
||||||
options: {
|
|
||||||
databaseId?: string;
|
|
||||||
isQuickstart?: boolean;
|
|
||||||
} = {}
|
|
||||||
): Promise<void> {
|
|
||||||
if (userContext.apiType === "Cassandra") {
|
if (userContext.apiType === "Cassandra") {
|
||||||
useSidePanel
|
useSidePanel
|
||||||
.getState()
|
.getState()
|
||||||
@@ -1144,13 +1119,10 @@ export default class Explorer {
|
|||||||
<CassandraAddCollectionPane explorer={this} cassandraApiClient={new CassandraAPIDataClient()} />
|
<CassandraAddCollectionPane explorer={this} cassandraApiClient={new CassandraAPIDataClient()} />
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
await useDatabases.getState().loadDatabaseOffers();
|
||||||
throughputCap && throughputCap !== -1
|
|
||||||
? await useDatabases.getState().loadAllOffers()
|
|
||||||
: await useDatabases.getState().loadDatabaseOffers();
|
|
||||||
useSidePanel
|
useSidePanel
|
||||||
.getState()
|
.getState()
|
||||||
.openSidePanel("New " + getCollectionName(), <AddCollectionPanel explorer={this} {...options} />);
|
.openSidePanel("New " + getCollectionName(), <AddCollectionPanel explorer={this} databaseId={databaseId} />);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1163,12 +1135,21 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _openSetupNotebooksPaneForQuickstart(): void {
|
||||||
|
const title = "Enable Notebooks (Preview)";
|
||||||
|
const description =
|
||||||
|
"You have not yet created a notebooks workspace for this account. To proceed and start using notebooks, we'll need to create a default notebooks workspace in this account.";
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel(title, <SetupNoteBooksPanel explorer={this} panelTitle={title} panelDescription={description} />);
|
||||||
|
}
|
||||||
|
|
||||||
public async handleOpenFileAction(path: string): Promise<void> {
|
public async handleOpenFileAction(path: string): Promise<void> {
|
||||||
if (useNotebook.getState().isPhoenixNotebooks === undefined) {
|
if (
|
||||||
await useNotebook.getState().getPhoenixStatus();
|
userContext.features.phoenix === false &&
|
||||||
}
|
!(await this._containsDefaultNotebookWorkspace(userContext.databaseAccount))
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
) {
|
||||||
await this.allocateContainer();
|
this._openSetupNotebooksPaneForQuickstart();
|
||||||
}
|
}
|
||||||
|
|
||||||
// We still use github urls like https://github.com/Azure-Samples/cosmos-notebooks/blob/master/CSharp_quickstarts/GettingStarted_CSharp.ipynb
|
// We still use github urls like https://github.com/Azure-Samples/cosmos-notebooks/blob/master/CSharp_quickstarts/GettingStarted_CSharp.ipynb
|
||||||
@@ -1199,7 +1180,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public openUploadFilePanel(parent?: NotebookContentItem): void {
|
public openUploadFilePanel(parent?: NotebookContentItem): void {
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
if (NotebookUtil.isPhoenixEnabled()) {
|
||||||
useDialog.getState().showOkCancelModalDialog(
|
useDialog.getState().showOkCancelModalDialog(
|
||||||
Notebook.newNotebookUploadModalTitle,
|
Notebook.newNotebookUploadModalTitle,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -1229,7 +1210,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getDownloadModalConent(fileName: string): JSX.Element {
|
public getDownloadModalConent(fileName: string): JSX.Element {
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
if (NotebookUtil.isPhoenixEnabled()) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p>{Notebook.galleryNotebookDownloadContent1}</p>
|
<p>{Notebook.galleryNotebookDownloadContent1}</p>
|
||||||
@@ -1251,24 +1232,28 @@ export default class Explorer {
|
|||||||
? this.refreshDatabaseForResourceToken()
|
? this.refreshDatabaseForResourceToken()
|
||||||
: this.refreshAllDatabases();
|
: this.refreshAllDatabases();
|
||||||
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
|
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
|
||||||
|
let isNotebookEnabled = true;
|
||||||
// TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount
|
if (!userContext.features.phoenix) {
|
||||||
const isNotebookEnabled =
|
isNotebookEnabled =
|
||||||
userContext.features.notebooksDownBanner ||
|
userContext.authType !== AuthType.ResourceToken &&
|
||||||
useNotebook.getState().isPhoenixNotebooks ||
|
((await this._containsDefaultNotebookWorkspace(userContext.databaseAccount)) ||
|
||||||
useNotebook.getState().isPhoenixFeatures;
|
userContext.features.enableNotebooks);
|
||||||
|
}
|
||||||
useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled);
|
useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled);
|
||||||
useNotebook
|
useNotebook.getState().setIsShellEnabled(isNotebookEnabled && isPublicInternetAccessAllowed());
|
||||||
.getState()
|
|
||||||
.setIsShellEnabled(useNotebook.getState().isPhoenixFeatures && isPublicInternetAccessAllowed());
|
|
||||||
|
|
||||||
TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, {
|
TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, {
|
||||||
isNotebookEnabled,
|
isNotebookEnabled,
|
||||||
dataExplorerArea: Constants.Areas.Notebook,
|
dataExplorerArea: Constants.Areas.Notebook,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
if (!userContext.features.notebooksTemporarilyDown) {
|
||||||
await this.initNotebooks(userContext.databaseAccount);
|
if (isNotebookEnabled) {
|
||||||
|
await this.initNotebooks(userContext.databaseAccount);
|
||||||
|
} else if (this.notebookToImport) {
|
||||||
|
// if notebooks is not enabled but the user is trying to do a quickstart setup with notebooks, open the SetupNotebooksPane
|
||||||
|
this._openSetupNotebooksPaneForQuickstart();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||||
import * as Q from "q";
|
import * as Q from "q";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
@@ -296,6 +294,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
this.setGremlinParams();
|
this.setGremlinParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectedNode = this.state.highlightedNode;
|
||||||
|
|
||||||
props.onGraphAccessorCreated({
|
props.onGraphAccessorCreated({
|
||||||
applyFilter: this.submitQuery.bind(this),
|
applyFilter: this.submitQuery.bind(this),
|
||||||
addVertex: this.addVertex.bind(this),
|
addVertex: this.addVertex.bind(this),
|
||||||
@@ -303,7 +303,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
});
|
});
|
||||||
} // constructor
|
} // constructor
|
||||||
|
|
||||||
public shareIGraphConfig(igraphConfig: IGraphConfig): void {
|
public shareIGraphConfig(igraphConfig: IGraphConfig) {
|
||||||
this.setState({
|
this.setState({
|
||||||
igraphConfig: { ...igraphConfig },
|
igraphConfig: { ...igraphConfig },
|
||||||
});
|
});
|
||||||
@@ -330,10 +330,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
const partitionKeyProperty = this.props.collectionPartitionKeyProperty;
|
const partitionKeyProperty = this.props.collectionPartitionKeyProperty;
|
||||||
|
|
||||||
// aggregate all the properties, remove dropped ones
|
// aggregate all the properties, remove dropped ones
|
||||||
const finalProperties = editedProperties.existingProperties.concat(editedProperties.addedProperties);
|
let finalProperties = editedProperties.existingProperties.concat(editedProperties.addedProperties);
|
||||||
|
|
||||||
// Compose the query
|
// Compose the query
|
||||||
const pkId = editedProperties.pkId;
|
let pkId = editedProperties.pkId;
|
||||||
let updateQueryFragment = "";
|
let updateQueryFragment = "";
|
||||||
|
|
||||||
finalProperties.forEach((p) => {
|
finalProperties.forEach((p) => {
|
||||||
@@ -422,7 +422,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
* Called from ko binding
|
* Called from ko binding
|
||||||
* @param id
|
* @param id
|
||||||
*/
|
*/
|
||||||
public selectNode(id: string): void {
|
public selectNode(id: string) {
|
||||||
if (!this.d3ForceGraph) {
|
if (!this.d3ForceGraph) {
|
||||||
console.warn("Attempting to select node, but d3ForceGraph not initialized, yet.");
|
console.warn("Attempting to select node, but d3ForceGraph not initialized, yet.");
|
||||||
return;
|
return;
|
||||||
@@ -431,7 +431,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
this.d3ForceGraph.selectNode(id);
|
this.d3ForceGraph.selectNode(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteHighlightedNode(): void {
|
public deleteHighlightedNode() {
|
||||||
if (!this.state.highlightedNode) {
|
if (!this.state.highlightedNode) {
|
||||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, "No highlighted node to remove.");
|
GraphExplorer.reportToConsole(ConsoleDataType.Error, "No highlighted node to remove.");
|
||||||
return;
|
return;
|
||||||
@@ -467,23 +467,23 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
* Is of type: {e: GremlinEdge, v: GremlinVertex}[]
|
* Is of type: {e: GremlinEdge, v: GremlinVertex}[]
|
||||||
* @param data
|
* @param data
|
||||||
*/
|
*/
|
||||||
public static isEdgeVertexPairArray(data: any): boolean {
|
public static isEdgeVertexPairArray(data: any) {
|
||||||
if (!(data instanceof Array)) {
|
if (!(data instanceof Array)) {
|
||||||
GraphExplorer.reportToConsole(ConsoleDataType.Info, "Query result not an array", data);
|
GraphExplorer.reportToConsole(ConsoleDataType.Info, "Query result not an array", data);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pairs: any[] = data;
|
let pairs: any[] = data;
|
||||||
for (let i = 0; i < pairs.length; i++) {
|
for (let i = 0; i < pairs.length; i++) {
|
||||||
const item = pairs[i];
|
const item = pairs[i];
|
||||||
if (
|
if (
|
||||||
!Object.prototype.hasOwnProperty.call(item, "e") ||
|
!item.hasOwnProperty("e") ||
|
||||||
!Object.prototype.hasOwnProperty.call(item, "v") ||
|
!item.hasOwnProperty("v") ||
|
||||||
!Object.prototype.hasOwnProperty.call(item["e"], "id") ||
|
!item["e"].hasOwnProperty("id") ||
|
||||||
!Object.prototype.hasOwnProperty.call(item["e"], "type") ||
|
!item["e"].hasOwnProperty("type") ||
|
||||||
item["e"].type !== "edge" ||
|
item["e"].type !== "edge" ||
|
||||||
!Object.prototype.hasOwnProperty.call(item["v"], "id") ||
|
!item["v"].hasOwnProperty("id") ||
|
||||||
!Object.prototype.hasOwnProperty.call(item["e"], "type") ||
|
!item["v"].hasOwnProperty("type") ||
|
||||||
item["v"].type !== "vertex"
|
item["v"].type !== "vertex"
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
@@ -514,7 +514,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
// Try hitting cache first
|
// Try hitting cache first
|
||||||
const cache = outE ? this.outECache : this.inECache;
|
const cache = outE ? this.outECache : this.inECache;
|
||||||
const pairs = cache.retrieve(vertex.id, startIndex, pageSize);
|
const pairs = cache.retrieve(vertex.id, startIndex, pageSize);
|
||||||
if (pairs !== null && pairs.length === pageSize) {
|
if (pairs != null && pairs.length === pageSize) {
|
||||||
const msg = `Retrieved ${pairs.length} ${outE ? "outE" : "inE"} edges from cache for vertex id: ${vertex.id}`;
|
const msg = `Retrieved ${pairs.length} ${outE ? "outE" : "inE"} edges from cache for vertex id: ${vertex.id}`;
|
||||||
GraphExplorer.reportToConsole(ConsoleDataType.Info, msg);
|
GraphExplorer.reportToConsole(ConsoleDataType.Info, msg);
|
||||||
return Q.resolve(pairs);
|
return Q.resolve(pairs);
|
||||||
@@ -588,6 +588,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
vertex._outEAllLoaded &&
|
vertex._outEAllLoaded &&
|
||||||
vertex._inEAllLoaded
|
vertex._inEAllLoaded
|
||||||
) {
|
) {
|
||||||
|
console.info("No more edges to load for vertex " + vertex.id);
|
||||||
updateGraphData();
|
updateGraphData();
|
||||||
return Q.resolve(graphData);
|
return Q.resolve(graphData);
|
||||||
}
|
}
|
||||||
@@ -667,7 +668,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return promise.then(() => {
|
return promise.then((nbPairsFetched: number) => {
|
||||||
if (offsetIndex >= GraphExplorer.LOAD_PAGE_SIZE || !vertex._outEAllLoaded || !vertex._inEAllLoaded) {
|
if (offsetIndex >= GraphExplorer.LOAD_PAGE_SIZE || !vertex._outEAllLoaded || !vertex._inEAllLoaded) {
|
||||||
vertex._pagination = {
|
vertex._pagination = {
|
||||||
total:
|
total:
|
||||||
@@ -753,7 +754,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
* Create a new edge in docdb and update graph
|
* Create a new edge in docdb and update graph
|
||||||
* @param e
|
* @param e
|
||||||
*/
|
*/
|
||||||
public createNewEdge(e: GraphNewEdgeData): Q.Promise<unknown> {
|
public createNewEdge(e: GraphNewEdgeData): Q.Promise<any> {
|
||||||
const q = `g.V('${GraphUtil.escapeSingleQuotes(e.inputOutV)}').addE('${GraphUtil.escapeSingleQuotes(
|
const q = `g.V('${GraphUtil.escapeSingleQuotes(e.inputOutV)}').addE('${GraphUtil.escapeSingleQuotes(
|
||||||
e.label
|
e.label
|
||||||
)}').To(g.V('${GraphUtil.escapeSingleQuotes(e.inputInV)}'))`;
|
)}').To(g.V('${GraphUtil.escapeSingleQuotes(e.inputInV)}'))`;
|
||||||
@@ -771,8 +772,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const edge = edges[0];
|
let edge = edges[0];
|
||||||
const graphData = this.originalGraphData;
|
let graphData = this.originalGraphData;
|
||||||
graphData.addEdge(edge);
|
graphData.addEdge(edge);
|
||||||
|
|
||||||
// Allow loadNeighbors to load list new edge
|
// Allow loadNeighbors to load list new edge
|
||||||
@@ -799,10 +800,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
* Manually update in-memory graph.
|
* Manually update in-memory graph.
|
||||||
* @param edgeId
|
* @param edgeId
|
||||||
*/
|
*/
|
||||||
public removeEdge(edgeId: string): Q.Promise<unknown> {
|
public removeEdge(edgeId: string): Q.Promise<any> {
|
||||||
return this.submitToBackend(`g.E('${GraphUtil.escapeSingleQuotes(edgeId)}').drop()`).then(
|
return this.submitToBackend(`g.E('${GraphUtil.escapeSingleQuotes(edgeId)}').drop()`).then(
|
||||||
() => {
|
() => {
|
||||||
const graphData = this.originalGraphData;
|
let graphData = this.originalGraphData;
|
||||||
graphData.removeEdge(edgeId, false);
|
graphData.removeEdge(edgeId, false);
|
||||||
this.updateGraphData(graphData, this.state.igraphConfig);
|
this.updateGraphData(graphData, this.state.igraphConfig);
|
||||||
},
|
},
|
||||||
@@ -825,14 +826,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const vertices: any[] = data;
|
let vertices: any[] = data;
|
||||||
if (vertices.length > 0) {
|
if (vertices.length > 0) {
|
||||||
const v0 = vertices[0];
|
let v0 = vertices[0];
|
||||||
if (
|
if (!v0.hasOwnProperty("id") || !v0.hasOwnProperty("type") || v0.type !== "vertex") {
|
||||||
!Object.prototype.hasOwnProperty.call(v0, "id") ||
|
|
||||||
!Object.prototype.hasOwnProperty.call(v0, "type") ||
|
|
||||||
v0.type !== "vertex"
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -840,7 +837,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
public processGremlinQueryResults(result: GremlinClient.GremlinRequestResult): void {
|
public processGremlinQueryResults(result: GremlinClient.GremlinRequestResult): void {
|
||||||
const data = result.data as GraphData.GremlinVertex[];
|
const data = result.data as any;
|
||||||
this.setFilterQueryStatus(FilterQueryStatus.GraphEmptyResult);
|
this.setFilterQueryStatus(FilterQueryStatus.GraphEmptyResult);
|
||||||
|
|
||||||
if (data === null) {
|
if (data === null) {
|
||||||
@@ -930,13 +927,13 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
throw { title: err };
|
throw { title: err };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vertices === null || vertices.length < 1) {
|
if (vertices == null || vertices.length < 1) {
|
||||||
const err = "Failed to create vertex (no vertex in response)";
|
const err = "Failed to create vertex (no vertex in response)";
|
||||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, err, vertices);
|
GraphExplorer.reportToConsole(ConsoleDataType.Error, err, vertices);
|
||||||
throw { title: err };
|
throw { title: err };
|
||||||
}
|
}
|
||||||
|
|
||||||
const vertex = vertices[0];
|
let vertex = vertices[0];
|
||||||
const graphData = this.originalGraphData;
|
const graphData = this.originalGraphData;
|
||||||
graphData.addVertex(vertex);
|
graphData.addVertex(vertex);
|
||||||
this.updateGraphData(graphData, this.state.igraphConfig);
|
this.updateGraphData(graphData, this.state.igraphConfig);
|
||||||
@@ -1025,7 +1022,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
this.gremlinClient.destroy();
|
this.gremlinClient.destroy();
|
||||||
}
|
}
|
||||||
public componentDidMount(): void {
|
public componentDidMount(): void {
|
||||||
if (this.props.onLoadStartKey !== null && this.props.onLoadStartKey !== undefined) {
|
if (this.props.onLoadStartKey != null && this.props.onLoadStartKey != undefined) {
|
||||||
TelemetryProcessor.traceSuccess(
|
TelemetryProcessor.traceSuccess(
|
||||||
Action.Tab,
|
Action.Tab,
|
||||||
{
|
{
|
||||||
@@ -1085,7 +1082,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
public static reportToConsole(type: ConsoleDataType.Info, msg: string, ...errorData: any[]): void;
|
public static reportToConsole(type: ConsoleDataType.Info, msg: string, ...errorData: any[]): void;
|
||||||
public static reportToConsole(type: ConsoleDataType.Error, msg: string, ...errorData: any[]): void;
|
public static reportToConsole(type: ConsoleDataType.Error, msg: string, ...errorData: any[]): void;
|
||||||
public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) {
|
public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) {
|
||||||
let errorDataStr = "";
|
let errorDataStr: string = "";
|
||||||
if (errorData && errorData.length > 0) {
|
if (errorData && errorData.length > 0) {
|
||||||
console.error(msg, errorData);
|
console.error(msg, errorData);
|
||||||
errorDataStr = ": " + JSON.stringify(errorData);
|
errorDataStr = ": " + JSON.stringify(errorData);
|
||||||
@@ -1164,15 +1161,12 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
)}"`
|
)}"`
|
||||||
).then(
|
).then(
|
||||||
(documents: DataModels.DocumentId[]) => {
|
(documents: DataModels.DocumentId[]) => {
|
||||||
$.each(
|
$.each(documents, (index: number, doc: any) => {
|
||||||
documents,
|
newIconsMap[doc["_graph_icon_property_value"]] = {
|
||||||
(index: number, doc: { _graph_icon_property_value: string; icon: string; format: string }) => {
|
data: doc["icon"],
|
||||||
newIconsMap[doc["_graph_icon_property_value"]] = {
|
format: doc["format"],
|
||||||
data: doc["icon"],
|
};
|
||||||
format: doc["format"],
|
});
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update graph configuration
|
// Update graph configuration
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -1229,8 +1223,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
const key = this.state.igraphConfig.nodeCaption;
|
const key = this.state.igraphConfig.nodeCaption;
|
||||||
return $.map(
|
return $.map(
|
||||||
this.state.rootMap,
|
this.state.rootMap,
|
||||||
(value: any): LeftPane.CaptionId => {
|
(value: any, index: number): LeftPane.CaptionId => {
|
||||||
const result = GraphData.GraphData.getNodePropValue(value, key);
|
let result = GraphData.GraphData.getNodePropValue(value, key);
|
||||||
return {
|
return {
|
||||||
caption: result !== undefined ? result : value.id,
|
caption: result !== undefined ? result : value.id,
|
||||||
id: value.id,
|
id: value.id,
|
||||||
@@ -1243,7 +1237,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
* Selecting a root node means
|
* Selecting a root node means
|
||||||
* @param node
|
* @param node
|
||||||
*/
|
*/
|
||||||
private selectRootNode(id: string): Q.Promise<unknown> {
|
private selectRootNode(id: string): Q.Promise<any> {
|
||||||
if (!this.d3ForceGraph) {
|
if (!this.d3ForceGraph) {
|
||||||
console.warn("Attempting to reset zoom, but d3ForceGraph not initialized, yet.");
|
console.warn("Attempting to reset zoom, but d3ForceGraph not initialized, yet.");
|
||||||
} else {
|
} else {
|
||||||
@@ -1288,7 +1282,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
this.collectNodeProperties(this.originalGraphData.vertices);
|
this.collectNodeProperties(this.originalGraphData.vertices);
|
||||||
this.updatePropertiesPane(id);
|
this.updatePropertiesPane(id);
|
||||||
},
|
},
|
||||||
(reason: string) => {
|
(reason: any) => {
|
||||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, `Failed to select root node. Reason:${reason}`);
|
GraphExplorer.reportToConsole(ConsoleDataType.Error, `Failed to select root node. Reason:${reason}`);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -1355,10 +1349,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
private getPkIdFromVertex(v: GraphData.GremlinVertex): string {
|
private getPkIdFromVertex(v: GraphData.GremlinVertex): string {
|
||||||
if (
|
if (
|
||||||
this.props.collectionPartitionKeyProperty &&
|
this.props.collectionPartitionKeyProperty &&
|
||||||
Object.prototype.hasOwnProperty.call(v, "properties") &&
|
v.hasOwnProperty("properties") &&
|
||||||
Object.prototype.hasOwnProperty.call(v.properties, this.props.collectionPartitionKeyProperty) &&
|
v.properties.hasOwnProperty(this.props.collectionPartitionKeyProperty) &&
|
||||||
v.properties[this.props.collectionPartitionKeyProperty].length > 0 &&
|
v.properties[this.props.collectionPartitionKeyProperty].length > 0 &&
|
||||||
Object.prototype.hasOwnProperty.call(v.properties[this.props.collectionPartitionKeyProperty][0], "value")
|
v.properties[this.props.collectionPartitionKeyProperty][0].hasOwnProperty("value")
|
||||||
) {
|
) {
|
||||||
const pk = v.properties[this.props.collectionPartitionKeyProperty][0].value;
|
const pk = v.properties[this.props.collectionPartitionKeyProperty][0].value;
|
||||||
return GraphExplorer.generatePkIdPair(pk, v.id);
|
return GraphExplorer.generatePkIdPair(pk, v.id);
|
||||||
@@ -1376,8 +1370,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
private getPkIdFromNodeData(v: GraphHighlightedNodeData): string {
|
private getPkIdFromNodeData(v: GraphHighlightedNodeData): string {
|
||||||
if (
|
if (
|
||||||
this.props.collectionPartitionKeyProperty &&
|
this.props.collectionPartitionKeyProperty &&
|
||||||
Object.prototype.hasOwnProperty.call(v, "properties") &&
|
v.hasOwnProperty("properties") &&
|
||||||
Object.prototype.hasOwnProperty.call(v.properties, this.props.collectionPartitionKeyProperty)
|
v.properties.hasOwnProperty(this.props.collectionPartitionKeyProperty)
|
||||||
) {
|
) {
|
||||||
const pk = v.properties[this.props.collectionPartitionKeyProperty];
|
const pk = v.properties[this.props.collectionPartitionKeyProperty];
|
||||||
return GraphExplorer.generatePkIdPair(pk[0] as PartitionKeyValueType, v.id);
|
return GraphExplorer.generatePkIdPair(pk[0] as PartitionKeyValueType, v.id);
|
||||||
@@ -1394,14 +1388,14 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
* @return id
|
* @return id
|
||||||
*/
|
*/
|
||||||
public static getPkIdFromDocumentId(d: DataModels.DocumentId, collectionPartitionKeyProperty: string): string {
|
public static getPkIdFromDocumentId(d: DataModels.DocumentId, collectionPartitionKeyProperty: string): string {
|
||||||
const { id } = d;
|
let { id } = d;
|
||||||
if (typeof id !== "string") {
|
if (typeof id !== "string") {
|
||||||
const error = `Vertex id is not a string: ${JSON.stringify(id)}.`;
|
const error = `Vertex id is not a string: ${JSON.stringify(id)}.`;
|
||||||
logConsoleError(error);
|
logConsoleError(error);
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (collectionPartitionKeyProperty && Object.prototype.hasOwnProperty.call(d, collectionPartitionKeyProperty)) {
|
if (collectionPartitionKeyProperty && d.hasOwnProperty(collectionPartitionKeyProperty)) {
|
||||||
let pk = (d as any)[collectionPartitionKeyProperty];
|
let pk = (d as any)[collectionPartitionKeyProperty];
|
||||||
if (typeof pk !== "string" && typeof pk !== "number" && typeof pk !== "boolean") {
|
if (typeof pk !== "string" && typeof pk !== "number" && typeof pk !== "boolean") {
|
||||||
if (Array.isArray(pk) && pk.length > 0) {
|
if (Array.isArray(pk) && pk.length > 0) {
|
||||||
@@ -1431,7 +1425,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
}"] AS p FROM c WHERE NOT IS_DEFINED(c._isEdge)`;
|
}"] AS p FROM c WHERE NOT IS_DEFINED(c._isEdge)`;
|
||||||
return this.executeNonPagedDocDbQuery(q).then(
|
return this.executeNonPagedDocDbQuery(q).then(
|
||||||
(documents: DataModels.DocumentId[]) => {
|
(documents: DataModels.DocumentId[]) => {
|
||||||
const possibleVertices = [] as PossibleVertex[];
|
let possibleVertices = [] as PossibleVertex[];
|
||||||
$.each(documents, (index: number, item: any) => {
|
$.each(documents, (index: number, item: any) => {
|
||||||
if (highlightedNodeId && item.id === highlightedNodeId) {
|
if (highlightedNodeId && item.id === highlightedNodeId) {
|
||||||
// Exclude highlighed node in the list
|
// Exclude highlighed node in the list
|
||||||
@@ -1445,7 +1439,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
caption: item.p,
|
caption: item.p,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (Object.prototype.hasOwnProperty.call(item, "p")) {
|
if (item.hasOwnProperty("p")) {
|
||||||
possibleVertices.push({
|
possibleVertices.push({
|
||||||
value: item.id,
|
value: item.id,
|
||||||
caption: item.p[0]["_value"],
|
caption: item.p[0]["_value"],
|
||||||
@@ -1468,17 +1462,17 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
* @param addedEdges
|
* @param addedEdges
|
||||||
* @return promise when done
|
* @return promise when done
|
||||||
*/
|
*/
|
||||||
private editGraphEdges(editedEdges: EditedEdges): Q.Promise<unknown> {
|
private editGraphEdges(editedEdges: EditedEdges): Q.Promise<any> {
|
||||||
const promises = [];
|
let promises = [];
|
||||||
// Drop edges
|
// Drop edges
|
||||||
for (let i = 0; i < editedEdges.droppedIds.length; i++) {
|
for (let i = 0; i < editedEdges.droppedIds.length; i++) {
|
||||||
const id = editedEdges.droppedIds[i];
|
let id = editedEdges.droppedIds[i];
|
||||||
promises.push(this.removeEdge(id));
|
promises.push(this.removeEdge(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add edges
|
// Add edges
|
||||||
for (let i = 0; i < editedEdges.addedEdges.length; i++) {
|
for (let i = 0; i < editedEdges.addedEdges.length; i++) {
|
||||||
const e = editedEdges.addedEdges[i];
|
let e = editedEdges.addedEdges[i];
|
||||||
promises.push(
|
promises.push(
|
||||||
this.createNewEdge(e).then(() => {
|
this.createNewEdge(e).then(() => {
|
||||||
// Reload neighbors in case we linked to a vertex that isn't loaded in the graph
|
// Reload neighbors in case we linked to a vertex that isn't loaded in the graph
|
||||||
@@ -1531,9 +1525,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
/**
|
/**
|
||||||
* For unit testing purposes
|
* For unit testing purposes
|
||||||
*/
|
*/
|
||||||
|
public onGraphUpdated(timestamp: number): void {}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
public onGraphUpdated(_timestamp: number): void {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get node properties for styling purposes. Result is the union of all properties of all nodes.
|
* Get node properties for styling purposes. Result is the union of all properties of all nodes.
|
||||||
@@ -1541,17 +1533,17 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
private collectNodeProperties(vertices: GraphData.GremlinVertex[]) {
|
private collectNodeProperties(vertices: GraphData.GremlinVertex[]) {
|
||||||
const props = {} as any; // Hashset
|
const props = {} as any; // Hashset
|
||||||
$.each(vertices, (index: number, item: GraphData.GremlinVertex) => {
|
$.each(vertices, (index: number, item: GraphData.GremlinVertex) => {
|
||||||
for (const p in item) {
|
for (var p in item) {
|
||||||
// DocDB: Exclude type because it's always 'vertex'
|
// DocDB: Exclude type because it's always 'vertex'
|
||||||
if (p !== "type" && typeof (item as any)[p] === "string") {
|
if (p !== "type" && typeof (item as any)[p] === "string") {
|
||||||
props[p] = true;
|
props[p] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Inspect properties
|
// Inspect properties
|
||||||
if (Object.prototype.hasOwnProperty.call(item, "properties")) {
|
if (item.hasOwnProperty("properties")) {
|
||||||
// TODO This is DocDB-graph specific
|
// TODO This is DocDB-graph specific
|
||||||
// Assume each property value is [{value:... }]
|
// Assume each property value is [{value:... }]
|
||||||
for (const f in item.properties) {
|
for (var f in item.properties) {
|
||||||
props[f] = true;
|
props[f] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1578,21 +1570,21 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = this.originalGraphData.getVertexById(id);
|
let data = this.originalGraphData.getVertexById(id);
|
||||||
|
|
||||||
// A bit of translation to make it easier to display
|
// A bit of translation to make it easier to display
|
||||||
const props: { [id: string]: ViewModels.GremlinPropertyValueType[] } = {};
|
let props: { [id: string]: ViewModels.GremlinPropertyValueType[] } = {};
|
||||||
for (const p in data.properties) {
|
for (let p in data.properties) {
|
||||||
props[p] = data.properties[p].map((gremlinProperty) => gremlinProperty.value);
|
props[p] = data.properties[p].map((gremlinProperty) => gremlinProperty.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// update neighbors
|
// update neighbors
|
||||||
const sources: NeighborVertexBasicInfo[] = [];
|
let sources: NeighborVertexBasicInfo[] = [];
|
||||||
const targets: NeighborVertexBasicInfo[] = [];
|
let targets: NeighborVertexBasicInfo[] = [];
|
||||||
this.props.onResetDefaultGraphConfigValues();
|
this.props.onResetDefaultGraphConfigValues();
|
||||||
const nodeCaption = this.state.igraphConfigUiData.nodeCaptionChoice;
|
let nodeCaption = this.state.igraphConfigUiData.nodeCaptionChoice;
|
||||||
this.updateSelectedNodeNeighbors(data.id, nodeCaption, sources, targets);
|
this.updateSelectedNodeNeighbors(data.id, nodeCaption, sources, targets);
|
||||||
const sData: GraphHighlightedNodeData = {
|
let sData: GraphHighlightedNodeData = {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
label: data.label,
|
label: data.label,
|
||||||
properties: props,
|
properties: props,
|
||||||
@@ -1619,16 +1611,16 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
targets: NeighborVertexBasicInfo[]
|
targets: NeighborVertexBasicInfo[]
|
||||||
): void {
|
): void {
|
||||||
// update neighbors
|
// update neighbors
|
||||||
const gd = this.originalGraphData;
|
let gd = this.originalGraphData;
|
||||||
const v = gd.getVertexById(id);
|
let v = gd.getVertexById(id);
|
||||||
|
|
||||||
// Clear the array while keeping the references
|
// Clear the array while keeping the references
|
||||||
sources.length = 0;
|
sources.length = 0;
|
||||||
targets.length = 0;
|
targets.length = 0;
|
||||||
|
|
||||||
const possibleEdgeLabels = {} as any; // Collect all edge labels in a hashset
|
let possibleEdgeLabels = {} as any; // Collect all edge labels in a hashset
|
||||||
|
|
||||||
for (const p in v.inE) {
|
for (let p in v.inE) {
|
||||||
possibleEdgeLabels[p] = true;
|
possibleEdgeLabels[p] = true;
|
||||||
const edges = v.inE[p];
|
const edges = v.inE[p];
|
||||||
$.each(edges, (index: number, edge: GraphData.GremlinShortInEdge) => {
|
$.each(edges, (index: number, edge: GraphData.GremlinShortInEdge) => {
|
||||||
@@ -1637,7 +1629,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
|
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
let caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
||||||
sources.push({
|
sources.push({
|
||||||
name: caption,
|
name: caption,
|
||||||
id: neighborId,
|
id: neighborId,
|
||||||
@@ -1647,7 +1639,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const p in v.outE) {
|
for (let p in v.outE) {
|
||||||
possibleEdgeLabels[p] = true;
|
possibleEdgeLabels[p] = true;
|
||||||
const edges = v.outE[p];
|
const edges = v.outE[p];
|
||||||
$.each(edges, (index: number, edge: GraphData.GremlinShortOutEdge) => {
|
$.each(edges, (index: number, edge: GraphData.GremlinShortOutEdge) => {
|
||||||
@@ -1656,7 +1648,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
|
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
let caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
||||||
targets.push({
|
targets.push({
|
||||||
name: caption,
|
name: caption,
|
||||||
id: neighborId,
|
id: neighborId,
|
||||||
@@ -1668,7 +1660,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
possibleEdgeLabels: Object.keys(possibleEdgeLabels).map(
|
possibleEdgeLabels: Object.keys(possibleEdgeLabels).map(
|
||||||
(value: string): InputTypeaheadComponent.Item => {
|
(value: string, index: number, array: string[]): InputTypeaheadComponent.Item => {
|
||||||
return { caption: value, value: value };
|
return { caption: value, value: value };
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
@@ -1689,20 +1681,20 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedVertex = vertices[0];
|
let updatedVertex = vertices[0];
|
||||||
if (this.originalGraphData.hasVertexId(updatedVertex.id)) {
|
if (this.originalGraphData.hasVertexId(updatedVertex.id)) {
|
||||||
const currentVertex = this.originalGraphData.getVertexById(updatedVertex.id);
|
let currentVertex = this.originalGraphData.getVertexById(updatedVertex.id);
|
||||||
// Copy updated properties
|
// Copy updated properties
|
||||||
if (Object.prototype.hasOwnProperty.call(currentVertex, "properties")) {
|
if (currentVertex.hasOwnProperty("properties")) {
|
||||||
delete currentVertex["properties"];
|
delete currentVertex["properties"];
|
||||||
}
|
}
|
||||||
for (const p in updatedVertex) {
|
for (var p in updatedVertex) {
|
||||||
(currentVertex as any)[p] = updatedVertex[p];
|
(currentVertex as any)[p] = updatedVertex[p];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO This kind of assumes saveVertexProperty is done from property panes.
|
// TODO This kind of assumes saveVertexProperty is done from property panes.
|
||||||
const hn = this.state.highlightedNode;
|
let hn = this.state.highlightedNode;
|
||||||
if (hn && hn.id === updatedVertex.id) {
|
if (hn && hn.id === updatedVertex.id) {
|
||||||
this.updatePropertiesPane(hn.id);
|
this.updatePropertiesPane(hn.id);
|
||||||
}
|
}
|
||||||
@@ -1716,7 +1708,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
igraphConfig?: IGraphConfig
|
igraphConfig?: IGraphConfig
|
||||||
) {
|
) {
|
||||||
this.originalGraphData = graphData;
|
this.originalGraphData = graphData;
|
||||||
const gd = JSON.parse(JSON.stringify(this.originalGraphData));
|
let gd = JSON.parse(JSON.stringify(this.originalGraphData));
|
||||||
if (!this.d3ForceGraph) {
|
if (!this.d3ForceGraph) {
|
||||||
console.warn("Attempting to update graph, but d3ForceGraph not initialized, yet.");
|
console.warn("Attempting to update graph, but d3ForceGraph not initialized, yet.");
|
||||||
return;
|
return;
|
||||||
@@ -1881,7 +1873,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
|
|
||||||
promise
|
promise
|
||||||
.then((result: GremlinClient.GremlinRequestResult) => this.processGremlinQueryResults(result))
|
.then((result: GremlinClient.GremlinRequestResult) => this.processGremlinQueryResults(result))
|
||||||
.catch((error: Error) => {
|
.catch((error: any) => {
|
||||||
const errorMsg = `Failed to process query result: ${getErrorMessage(error)}`;
|
const errorMsg = `Failed to process query result: ${getErrorMessage(error)}`;
|
||||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export class LeftPaneComponent extends React.Component<LeftPaneComponentProps> {
|
|||||||
className={className}
|
className={className}
|
||||||
as="tr"
|
as="tr"
|
||||||
aria-label={node.caption}
|
aria-label={node.caption}
|
||||||
onActivated={() => this.props.onRootNodeSelected(node.id)}
|
onActivated={(e) => this.props.onRootNodeSelected(node.id)}
|
||||||
key={node.id}
|
key={node.id}
|
||||||
>
|
>
|
||||||
<td className="resultItem">
|
<td className="resultItem">
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import React from "react";
|
||||||
import { mount, ReactWrapper } from "enzyme";
|
import { mount, ReactWrapper } from "enzyme";
|
||||||
import * as Q from "q";
|
import * as Q from "q";
|
||||||
import React from "react";
|
import { NodePropertiesComponent, NodePropertiesComponentProps, Mode } from "./NodePropertiesComponent";
|
||||||
import { GraphHighlightedNodeData, PossibleVertex } from "./GraphExplorer";
|
import { GraphHighlightedNodeData, EditedProperties, EditedEdges, PossibleVertex } from "./GraphExplorer";
|
||||||
import { Mode, NodePropertiesComponent, NodePropertiesComponentProps } from "./NodePropertiesComponent";
|
|
||||||
|
|
||||||
describe("Property pane", () => {
|
describe("Property pane", () => {
|
||||||
const title = "My Title";
|
const title = "My Title";
|
||||||
@@ -37,18 +37,17 @@ describe("Property pane", () => {
|
|||||||
return {
|
return {
|
||||||
expandedTitle: title,
|
expandedTitle: title,
|
||||||
isCollapsed: false,
|
isCollapsed: false,
|
||||||
onCollapsedChanged: jest.fn(),
|
onCollapsedChanged: (newValue: boolean): void => {},
|
||||||
node: highlightedNode,
|
node: highlightedNode,
|
||||||
getPkIdFromNodeData: (): string => undefined,
|
getPkIdFromNodeData: (v: GraphHighlightedNodeData): string => null,
|
||||||
collectionPartitionKeyProperty: undefined,
|
collectionPartitionKeyProperty: null,
|
||||||
updateVertexProperties: (): Q.Promise<void> => Q.resolve(),
|
updateVertexProperties: (editedProperties: EditedProperties): Q.Promise<void> => Q.resolve(),
|
||||||
selectNode: jest.fn(),
|
selectNode: (id: string): void => {},
|
||||||
updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(undefined),
|
updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(null),
|
||||||
possibleEdgeLabels: undefined,
|
possibleEdgeLabels: null,
|
||||||
//eslint-disable-next-line
|
editGraphEdges: (editedEdges: EditedEdges): Q.Promise<any> => Q.resolve(),
|
||||||
editGraphEdges: (): Q.Promise<any> => Q.resolve(),
|
deleteHighlightedNode: (): void => {},
|
||||||
deleteHighlightedNode: jest.fn(),
|
onModeChanged: (newMode: Mode): void => {},
|
||||||
onModeChanged: jest.fn(),
|
|
||||||
viewMode: Mode.READONLY_PROP,
|
viewMode: Mode.READONLY_PROP,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export class NodePropertiesComponent extends React.Component<
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
editedProperties: {
|
editedProperties: {
|
||||||
pkId: undefined,
|
pkId: null,
|
||||||
readOnlyProperties: [],
|
readOnlyProperties: [],
|
||||||
existingProperties: [],
|
existingProperties: [],
|
||||||
addedProperties: [],
|
addedProperties: [],
|
||||||
@@ -98,12 +98,15 @@ export class NodePropertiesComponent extends React.Component<
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getDerivedStateFromProps(props: NodePropertiesComponentProps): Partial<NodePropertiesComponentState> {
|
public static getDerivedStateFromProps(
|
||||||
|
props: NodePropertiesComponentProps,
|
||||||
|
state: NodePropertiesComponentState
|
||||||
|
): Partial<NodePropertiesComponentState> {
|
||||||
if (props.viewMode !== Mode.READONLY_PROP) {
|
if (props.viewMode !== Mode.READONLY_PROP) {
|
||||||
return { isDeleteConfirm: false };
|
return { isDeleteConfirm: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
@@ -135,10 +138,10 @@ export class NodePropertiesComponent extends React.Component<
|
|||||||
* @param value
|
* @param value
|
||||||
*/
|
*/
|
||||||
private static getTypeOption(value: any): ViewModels.InputPropertyValueTypeString {
|
private static getTypeOption(value: any): ViewModels.InputPropertyValueTypeString {
|
||||||
if (value === undefined) {
|
if (value == null) {
|
||||||
return "null";
|
return "null";
|
||||||
}
|
}
|
||||||
const type = typeof value;
|
let type = typeof value;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "number":
|
case "number":
|
||||||
case "boolean":
|
case "boolean":
|
||||||
@@ -169,9 +172,10 @@ export class NodePropertiesComponent extends React.Component<
|
|||||||
];
|
];
|
||||||
|
|
||||||
const existingProps: ViewModels.InputProperty[] = [];
|
const existingProps: ViewModels.InputProperty[] = [];
|
||||||
|
|
||||||
if (this.props.node.hasOwnProperty("properties")) {
|
if (this.props.node.hasOwnProperty("properties")) {
|
||||||
const hProps = this.props.node["properties"];
|
const hProps = this.props.node["properties"];
|
||||||
for (const p in hProps) {
|
for (let p in hProps) {
|
||||||
const propValues = hProps[p];
|
const propValues = hProps[p];
|
||||||
(p === partitionKeyProperty ? readOnlyProps : existingProps).push({
|
(p === partitionKeyProperty ? readOnlyProps : existingProps).push({
|
||||||
key: p,
|
key: p,
|
||||||
@@ -433,7 +437,7 @@ export class NodePropertiesComponent extends React.Component<
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,15 @@
|
|||||||
* and update any knockout observables passed from the parent.
|
* and update any knockout observables passed from the parent.
|
||||||
*/
|
*/
|
||||||
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
|
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
|
||||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import create, { UseStore } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { StyleConstants } from "../../../Common/Constants";
|
import { StyleConstants } from "../../../Common/Constants";
|
||||||
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
|
import { userContext } from "../../../UserContext";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
import { NotebookUtil } from "../../Notebook/NotebookUtil";
|
||||||
import { useSelectedNode } from "../../useSelectedNode";
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
||||||
import * as CommandBarUtil from "./CommandBarUtil";
|
import * as CommandBarUtil from "./CommandBarUtil";
|
||||||
@@ -53,10 +56,18 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
|||||||
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
||||||
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
||||||
|
|
||||||
if (useNotebook.getState().isPhoenixNotebooks || useNotebook.getState().isPhoenixFeatures) {
|
if (NotebookUtil.isPhoenixEnabled()) {
|
||||||
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus(container, "connectionStatus"));
|
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus(container, "connectionStatus"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
userContext.features.phoenix === false &&
|
||||||
|
userContext.features.notebooksTemporarilyDown === false &&
|
||||||
|
useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2
|
||||||
|
) {
|
||||||
|
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="commandBarContainer">
|
<div className="commandBarContainer">
|
||||||
<FluentCommandBar
|
<FluentCommandBar
|
||||||
|
|||||||
@@ -31,13 +31,28 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Button should be visible", () => {
|
it("Account is not serverless - button should be visible", () => {
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const enableAzureSynapseLinkBtn = buttons.find(
|
const enableAzureSynapseLinkBtn = buttons.find(
|
||||||
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
|
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
|
||||||
);
|
);
|
||||||
expect(enableAzureSynapseLinkBtn).toBeDefined();
|
expect(enableAzureSynapseLinkBtn).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Account is serverless - button should be hidden", () => {
|
||||||
|
updateUserContext({
|
||||||
|
databaseAccount: {
|
||||||
|
properties: {
|
||||||
|
capabilities: [{ name: "EnableServerless" }],
|
||||||
|
},
|
||||||
|
} as DatabaseAccount,
|
||||||
|
});
|
||||||
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
|
const enableAzureSynapseLinkBtn = buttons.find(
|
||||||
|
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
|
||||||
|
);
|
||||||
|
expect(enableAzureSynapseLinkBtn).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Enable notebook button", () => {
|
describe("Enable notebook button", () => {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg";
|
|||||||
import FeedbackIcon from "../../../../images/Feedback-Command.svg";
|
import FeedbackIcon from "../../../../images/Feedback-Command.svg";
|
||||||
import GitHubIcon from "../../../../images/github.svg";
|
import GitHubIcon from "../../../../images/github.svg";
|
||||||
import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg";
|
import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg";
|
||||||
|
import EnableNotebooksIcon from "../../../../images/notebook/Notebook-enable.svg";
|
||||||
import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg";
|
import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg";
|
||||||
import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg";
|
import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg";
|
||||||
import OpenInTabIcon from "../../../../images/open-in-tab.svg";
|
import OpenInTabIcon from "../../../../images/open-in-tab.svg";
|
||||||
@@ -24,6 +25,7 @@ import { useSidePanel } from "../../../hooks/useSidePanel";
|
|||||||
import { JunoClient } from "../../../Juno/JunoClient";
|
import { JunoClient } from "../../../Juno/JunoClient";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils";
|
import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils";
|
||||||
|
import { isServerlessAccount } from "../../../Utils/CapabilityUtils";
|
||||||
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
|
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
@@ -34,6 +36,7 @@ import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPa
|
|||||||
import { GitHubReposPanel } from "../../Panes/GitHubReposPanel/GitHubReposPanel";
|
import { GitHubReposPanel } from "../../Panes/GitHubReposPanel/GitHubReposPanel";
|
||||||
import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane";
|
import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane";
|
||||||
import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane";
|
import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane";
|
||||||
|
import { SetupNoteBooksPanel } from "../../Panes/SetupNotebooksPanel/SetupNotebooksPanel";
|
||||||
import { useDatabases } from "../../useDatabases";
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { SelectedNodeState } from "../../useSelectedNode";
|
import { SelectedNodeState } from "../../useSelectedNode";
|
||||||
|
|
||||||
@@ -75,10 +78,9 @@ export function createStaticCommandBarButtons(
|
|||||||
if (container.notebookManager?.gitHubOAuthService) {
|
if (container.notebookManager?.gitHubOAuthService) {
|
||||||
notebookButtons.push(createManageGitHubAccountButton(container));
|
notebookButtons.push(createManageGitHubAccountButton(container));
|
||||||
}
|
}
|
||||||
if (useNotebook.getState().isPhoenixFeatures && configContext.isTerminalEnabled) {
|
|
||||||
notebookButtons.push(createOpenTerminalButton(container));
|
notebookButtons.push(createOpenTerminalButton(container));
|
||||||
}
|
if (userContext.features.phoenix === false) {
|
||||||
if (useNotebook.getState().isPhoenixNotebooks && selectedNodeState.isConnectedToContainer()) {
|
|
||||||
notebookButtons.push(createNotebookWorkspaceResetButton(container));
|
notebookButtons.push(createNotebookWorkspaceResetButton(container));
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -96,19 +98,22 @@ export function createStaticCommandBarButtons(
|
|||||||
}
|
}
|
||||||
|
|
||||||
notebookButtons.forEach((btn) => {
|
notebookButtons.forEach((btn) => {
|
||||||
if (btn.commandButtonLabel.indexOf("Cassandra") !== -1) {
|
if (userContext.features.notebooksTemporarilyDown) {
|
||||||
if (!useNotebook.getState().isPhoenixFeatures) {
|
if (btn.commandButtonLabel.indexOf("Cassandra") !== -1) {
|
||||||
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.cassandraShellTemporarilyDownMsg);
|
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.cassandraShellTemporarilyDownMsg);
|
||||||
}
|
} else if (btn.commandButtonLabel.indexOf("Mongo") !== -1) {
|
||||||
} else if (btn.commandButtonLabel.indexOf("Mongo") !== -1) {
|
|
||||||
if (!useNotebook.getState().isPhoenixFeatures) {
|
|
||||||
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.mongoShellTemporarilyDownMsg);
|
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.mongoShellTemporarilyDownMsg);
|
||||||
|
} else {
|
||||||
|
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg);
|
||||||
}
|
}
|
||||||
} else if (!useNotebook.getState().isPhoenixNotebooks) {
|
|
||||||
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg);
|
|
||||||
}
|
}
|
||||||
buttons.push(btn);
|
buttons.push(btn);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
if (!isRunningOnNationalCloud() && !userContext.features.notebooksTemporarilyDown) {
|
||||||
|
buttons.push(createDivider());
|
||||||
|
buttons.push(createEnableNotebooksButton(container));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedNodeState.isDatabaseNodeOrNoneSelected()) {
|
if (!selectedNodeState.isDatabaseNodeOrNoneSelected()) {
|
||||||
@@ -163,7 +168,9 @@ export function createContextCommandBarButtons(
|
|||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||||
if (useNotebook.getState().isShellEnabled) {
|
if (useNotebook.getState().isShellEnabled) {
|
||||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
if (!userContext.features.notebooksTemporarilyDown) {
|
||||||
|
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedCollection && selectedCollection.onNewMongoShellClick();
|
selectedCollection && selectedCollection.onNewMongoShellClick();
|
||||||
}
|
}
|
||||||
@@ -171,6 +178,13 @@ export function createContextCommandBarButtons(
|
|||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
hasPopup: true,
|
hasPopup: true,
|
||||||
|
tooltipText:
|
||||||
|
useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown
|
||||||
|
? Constants.Notebook.mongoShellTemporarilyDownMsg
|
||||||
|
: undefined,
|
||||||
|
disabled:
|
||||||
|
(selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") ||
|
||||||
|
(useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown),
|
||||||
};
|
};
|
||||||
buttons.push(newMongoShellBtn);
|
buttons.push(newMongoShellBtn);
|
||||||
}
|
}
|
||||||
@@ -266,6 +280,10 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isServerlessAccount()) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (userContext?.databaseAccount?.properties?.enableAnalyticalStorage) {
|
if (userContext?.databaseAccount?.properties?.enableAnalyticalStorage) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -292,13 +310,8 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps {
|
|||||||
return {
|
return {
|
||||||
iconSrc: AddDatabaseIcon,
|
iconSrc: AddDatabaseIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: async () => {
|
onCommandClick: () =>
|
||||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
useSidePanel.getState().openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={container} />),
|
||||||
if (throughputCap && throughputCap !== -1) {
|
|
||||||
await useDatabases.getState().loadAllOffers();
|
|
||||||
}
|
|
||||||
useSidePanel.getState().openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={container} />);
|
|
||||||
},
|
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
hasPopup: true,
|
hasPopup: true,
|
||||||
@@ -309,7 +322,6 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB
|
|||||||
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
|
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
|
||||||
const label = "New SQL Query";
|
const label = "New SQL Query";
|
||||||
return {
|
return {
|
||||||
id: "newQueryBtn",
|
|
||||||
iconSrc: AddSqlQueryIcon,
|
iconSrc: AddSqlQueryIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
@@ -324,7 +336,6 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB
|
|||||||
} else if (userContext.apiType === "Mongo") {
|
} else if (userContext.apiType === "Mongo") {
|
||||||
const label = "New Query";
|
const label = "New Query";
|
||||||
return {
|
return {
|
||||||
id: "newQueryBtn",
|
|
||||||
iconSrc: AddSqlQueryIcon,
|
iconSrc: AddSqlQueryIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
@@ -411,7 +422,6 @@ function applyNotebooksTemporarilyDownStyle(buttonProps: CommandButtonComponentP
|
|||||||
function createNewNotebookButton(container: Explorer): CommandButtonComponentProps {
|
function createNewNotebookButton(container: Explorer): CommandButtonComponentProps {
|
||||||
const label = "New Notebook";
|
const label = "New Notebook";
|
||||||
return {
|
return {
|
||||||
id: "newNotebookBtn",
|
|
||||||
iconSrc: NewNotebookIcon,
|
iconSrc: NewNotebookIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: () => container.onNewNotebookClicked(),
|
onCommandClick: () => container.onNewNotebookClicked(),
|
||||||
@@ -462,6 +472,33 @@ function createOpenQueryFromDiskButton(): CommandButtonComponentProps {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createEnableNotebooksButton(container: Explorer): CommandButtonComponentProps {
|
||||||
|
if (configContext.platform === Platform.Emulator) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const label = "Enable Notebooks (Preview)";
|
||||||
|
const tooltip =
|
||||||
|
"Notebooks are not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
|
||||||
|
const description =
|
||||||
|
"Looks like you have not yet created a notebooks workspace for this account. To proceed and start using notebooks, we'll need to create a default notebooks workspace in this account.";
|
||||||
|
return {
|
||||||
|
iconSrc: EnableNotebooksIcon,
|
||||||
|
iconAlt: label,
|
||||||
|
onCommandClick: () =>
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel(
|
||||||
|
label,
|
||||||
|
<SetupNoteBooksPanel explorer={container} panelTitle={label} panelDescription={description} />
|
||||||
|
),
|
||||||
|
commandButtonLabel: label,
|
||||||
|
hasPopup: false,
|
||||||
|
disabled: !useNotebook.getState().isNotebooksEnabledForAccount,
|
||||||
|
ariaLabel: label,
|
||||||
|
tooltipText: useNotebook.getState().isNotebooksEnabledForAccount ? "" : tooltip,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function createOpenTerminalButton(container: Explorer): CommandButtonComponentProps {
|
function createOpenTerminalButton(container: Explorer): CommandButtonComponentProps {
|
||||||
const label = "Open Terminal";
|
const label = "Open Terminal";
|
||||||
return {
|
return {
|
||||||
@@ -479,6 +516,9 @@ function createOpenMongoTerminalButton(container: Explorer): CommandButtonCompon
|
|||||||
const label = "Open Mongo Shell";
|
const label = "Open Mongo Shell";
|
||||||
const tooltip =
|
const tooltip =
|
||||||
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
|
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
|
||||||
|
const title = "Set up workspace";
|
||||||
|
const description =
|
||||||
|
"Looks like you have not created a workspace for this account. To proceed and start using features including mongo shell and notebook, we will need to create a default workspace in this account.";
|
||||||
const disableButton =
|
const disableButton =
|
||||||
!useNotebook.getState().isNotebooksEnabledForAccount && !useNotebook.getState().isNotebookEnabled;
|
!useNotebook.getState().isNotebooksEnabledForAccount && !useNotebook.getState().isNotebookEnabled;
|
||||||
return {
|
return {
|
||||||
@@ -487,6 +527,13 @@ function createOpenMongoTerminalButton(container: Explorer): CommandButtonCompon
|
|||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
if (useNotebook.getState().isNotebookEnabled) {
|
if (useNotebook.getState().isNotebookEnabled) {
|
||||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||||
|
} else {
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel(
|
||||||
|
title,
|
||||||
|
<SetupNoteBooksPanel explorer={container} panelTitle={title} panelDescription={description} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
@@ -501,6 +548,9 @@ function createOpenCassandraTerminalButton(container: Explorer): CommandButtonCo
|
|||||||
const label = "Open Cassandra Shell";
|
const label = "Open Cassandra Shell";
|
||||||
const tooltip =
|
const tooltip =
|
||||||
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
|
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
|
||||||
|
const title = "Set up workspace";
|
||||||
|
const description =
|
||||||
|
"Looks like you have not created a workspace for this account. To proceed and start using features including cassandra shell and notebook, we will need to create a default workspace in this account.";
|
||||||
const disableButton =
|
const disableButton =
|
||||||
!useNotebook.getState().isNotebooksEnabledForAccount && !useNotebook.getState().isNotebookEnabled;
|
!useNotebook.getState().isNotebooksEnabledForAccount && !useNotebook.getState().isNotebookEnabled;
|
||||||
return {
|
return {
|
||||||
@@ -509,6 +559,13 @@ function createOpenCassandraTerminalButton(container: Explorer): CommandButtonCo
|
|||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
if (useNotebook.getState().isNotebookEnabled) {
|
if (useNotebook.getState().isNotebookEnabled) {
|
||||||
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
|
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
|
||||||
|
} else {
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel(
|
||||||
|
title,
|
||||||
|
<SetupNoteBooksPanel explorer={container} panelTitle={title} panelDescription={description} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
|
|||||||
@@ -1,20 +1,8 @@
|
|||||||
import {
|
import { Icon, ProgressIndicator, Stack, TooltipHost } from "@fluentui/react";
|
||||||
FocusTrapCallout,
|
import { ActionButton } from "@fluentui/react/lib/Button";
|
||||||
FocusZone,
|
|
||||||
FocusZoneTabbableElements,
|
|
||||||
FontWeights,
|
|
||||||
Icon,
|
|
||||||
mergeStyleSets,
|
|
||||||
ProgressIndicator,
|
|
||||||
Stack,
|
|
||||||
Text,
|
|
||||||
TooltipHost,
|
|
||||||
} from "@fluentui/react";
|
|
||||||
import { useId } from "@fluentui/react-hooks";
|
|
||||||
import { ActionButton, DefaultButton } from "@fluentui/react/lib/Button";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import "../../../../less/hostedexplorer.less";
|
import "../../../../less/hostedexplorer.less";
|
||||||
import { ConnectionStatusType, ContainerStatusType, Notebook } from "../../../Common/Constants";
|
import { ConnectionStatusType, Notebook } from "../../../Common/Constants";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { useNotebook } from "../../Notebook/useNotebook";
|
import { useNotebook } from "../../Notebook/useNotebook";
|
||||||
import "../CommandBar/ConnectionStatusComponent.less";
|
import "../CommandBar/ConnectionStatusComponent.less";
|
||||||
@@ -22,33 +10,12 @@ interface Props {
|
|||||||
container: Explorer;
|
container: Explorer;
|
||||||
}
|
}
|
||||||
export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Element => {
|
export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Element => {
|
||||||
const connectionInfo = useNotebook((state) => state.connectionInfo);
|
|
||||||
const [second, setSecond] = React.useState("00");
|
const [second, setSecond] = React.useState("00");
|
||||||
const [minute, setMinute] = React.useState("00");
|
const [minute, setMinute] = React.useState("00");
|
||||||
const [isActive, setIsActive] = React.useState(false);
|
const [isActive, setIsActive] = React.useState(false);
|
||||||
const [counter, setCounter] = React.useState(0);
|
const [counter, setCounter] = React.useState(0);
|
||||||
const [statusColor, setStatusColor] = React.useState("");
|
const [statusColor, setStatusColor] = React.useState("");
|
||||||
const [toolTipContent, setToolTipContent] = React.useState("Connect to temporary workspace.");
|
const [toolTipContent, setToolTipContent] = React.useState("Connect to temporary workspace.");
|
||||||
const [isBarDismissed, setIsBarDismissed] = React.useState<boolean>(false);
|
|
||||||
const buttonId = useId("callout-button");
|
|
||||||
const containerInfo = useNotebook((state) => state.containerStatus);
|
|
||||||
|
|
||||||
const styles = mergeStyleSets({
|
|
||||||
callout: {
|
|
||||||
width: 320,
|
|
||||||
padding: "20px 24px",
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
marginBottom: 12,
|
|
||||||
fontWeight: FontWeights.semilight,
|
|
||||||
},
|
|
||||||
buttons: {
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "flex-end",
|
|
||||||
marginTop: 20,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
let intervalId: NodeJS.Timeout;
|
let intervalId: NodeJS.Timeout;
|
||||||
|
|
||||||
@@ -68,15 +35,6 @@ export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Ele
|
|||||||
return () => clearInterval(intervalId);
|
return () => clearInterval(intervalId);
|
||||||
}, [isActive, counter]);
|
}, [isActive, counter]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (connectionInfo?.status === ConnectionStatusType.Reconnect) {
|
|
||||||
setToolTipContent("Click here to Reconnect to temporary workspace.");
|
|
||||||
} else if (connectionInfo?.status === ConnectionStatusType.Failed) {
|
|
||||||
setStatusColor("status failed is-animating");
|
|
||||||
setToolTipContent("Click here to Reconnect to temporary workspace.");
|
|
||||||
}
|
|
||||||
}, [connectionInfo.status]);
|
|
||||||
|
|
||||||
const stopTimer = () => {
|
const stopTimer = () => {
|
||||||
setIsActive(false);
|
setIsActive(false);
|
||||||
setCounter(0);
|
setCounter(0);
|
||||||
@@ -84,13 +42,15 @@ export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Ele
|
|||||||
setMinute("00");
|
setMinute("00");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const connectionInfo = useNotebook((state) => state.connectionInfo);
|
||||||
const memoryUsageInfo = useNotebook((state) => state.memoryUsageInfo);
|
const memoryUsageInfo = useNotebook((state) => state.memoryUsageInfo);
|
||||||
|
|
||||||
const totalGB = memoryUsageInfo ? memoryUsageInfo.totalKB / Notebook.memoryGuageToGB : 0;
|
const totalGB = memoryUsageInfo ? memoryUsageInfo.totalKB / Notebook.memoryGuageToGB : 0;
|
||||||
const usedGB = totalGB > 0 ? totalGB - memoryUsageInfo.freeKB / Notebook.memoryGuageToGB : 0;
|
const usedGB = totalGB > 0 ? totalGB - memoryUsageInfo.freeKB / Notebook.memoryGuageToGB : 0;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
connectionInfo &&
|
connectionInfo &&
|
||||||
(connectionInfo.status === ConnectionStatusType.Connect || connectionInfo.status === ConnectionStatusType.Reconnect)
|
(connectionInfo.status === ConnectionStatusType.Connect || connectionInfo.status === ConnectionStatusType.ReConnect)
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<ActionButton className="commandReactBtn" onClick={() => container.allocateContainer()}>
|
<ActionButton className="commandReactBtn" onClick={() => container.allocateContainer()}>
|
||||||
@@ -105,7 +65,6 @@ export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Ele
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connecting && isActive === false) {
|
if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connecting && isActive === false) {
|
||||||
stopTimer();
|
|
||||||
setIsActive(true);
|
setIsActive(true);
|
||||||
setStatusColor("status connecting is-animating");
|
setStatusColor("status connecting is-animating");
|
||||||
setToolTipContent("Connecting to temporary workspace.");
|
setToolTipContent("Connecting to temporary workspace.");
|
||||||
@@ -119,68 +78,30 @@ export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Ele
|
|||||||
setToolTipContent("Click here to Reconnect to temporary workspace.");
|
setToolTipContent("Click here to Reconnect to temporary workspace.");
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<ActionButton
|
||||||
<TooltipHost
|
className={connectionInfo.status === ConnectionStatusType.Failed ? "commandReactBtn" : "connectedReactBtn"}
|
||||||
content={
|
onClick={(e: React.MouseEvent<HTMLSpanElement>) =>
|
||||||
containerInfo?.status === ContainerStatusType.Active
|
connectionInfo.status === ConnectionStatusType.Failed ? container.allocateContainer() : e.preventDefault()
|
||||||
? `Connected to temporary workspace. This temporary workspace will get disconnected in ${Math.round(
|
}
|
||||||
containerInfo.durationLeftInMinutes
|
>
|
||||||
)} minutes.`
|
<TooltipHost content={toolTipContent}>
|
||||||
: toolTipContent
|
<Stack className="connectionStatusContainer" horizontal>
|
||||||
}
|
<i className={statusColor}></i>
|
||||||
>
|
<span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}>
|
||||||
<ActionButton
|
{connectionInfo.status}
|
||||||
id={buttonId}
|
</span>
|
||||||
className={connectionInfo.status === ConnectionStatusType.Failed ? "commandReactBtn" : "connectedReactBtn"}
|
{connectionInfo.status === ConnectionStatusType.Connecting && isActive && (
|
||||||
onClick={(e: React.MouseEvent<HTMLSpanElement>) =>
|
<ProgressIndicator description={minute + ":" + second} />
|
||||||
connectionInfo.status === ConnectionStatusType.Failed ? container.allocateContainer() : e.preventDefault()
|
)}
|
||||||
}
|
{connectionInfo.status === ConnectionStatusType.Connected && !isActive && (
|
||||||
>
|
<ProgressIndicator
|
||||||
<Stack className="connectionStatusContainer" horizontal>
|
className={usedGB / totalGB > 0.8 ? "lowMemory" : ""}
|
||||||
<i className={statusColor}></i>
|
description={usedGB.toFixed(1) + " of " + totalGB.toFixed(1) + " GB"}
|
||||||
<span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}>
|
percentComplete={usedGB / totalGB}
|
||||||
{connectionInfo.status}
|
/>
|
||||||
</span>
|
)}
|
||||||
{connectionInfo.status === ConnectionStatusType.Connecting && isActive && (
|
</Stack>
|
||||||
<ProgressIndicator description={minute + ":" + second} />
|
|
||||||
)}
|
|
||||||
{connectionInfo.status === ConnectionStatusType.Connected && !isActive && (
|
|
||||||
<ProgressIndicator
|
|
||||||
className={totalGB !== 0 && usedGB / totalGB > 0.8 ? "lowMemory" : ""}
|
|
||||||
description={usedGB.toFixed(1) + " of " + totalGB.toFixed(1) + " GB"}
|
|
||||||
percentComplete={totalGB !== 0 ? usedGB / totalGB : 0}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
{!isBarDismissed &&
|
|
||||||
containerInfo.status &&
|
|
||||||
containerInfo.status === ContainerStatusType.Active &&
|
|
||||||
Math.round(containerInfo.durationLeftInMinutes) <= Notebook.remainingTimeForAlert ? (
|
|
||||||
<FocusTrapCallout
|
|
||||||
role="alertdialog"
|
|
||||||
className={styles.callout}
|
|
||||||
gapSpace={0}
|
|
||||||
target={`#${buttonId}`}
|
|
||||||
onDismiss={() => setIsBarDismissed(true)}
|
|
||||||
setInitialFocus
|
|
||||||
>
|
|
||||||
<Text block variant="xLarge" className={styles.title}>
|
|
||||||
Remaining Time
|
|
||||||
</Text>
|
|
||||||
<Text block variant="small">
|
|
||||||
This temporary workspace will get disconnected in {Math.round(containerInfo.durationLeftInMinutes)}{" "}
|
|
||||||
minutes. To save your work permanently, save your notebooks to a GitHub repository or download the
|
|
||||||
notebooks to your local machine before the session ends.
|
|
||||||
</Text>
|
|
||||||
<FocusZone handleTabKey={FocusZoneTabbableElements.all} isCircularNavigation>
|
|
||||||
<Stack className={styles.buttons} gap={8} horizontal>
|
|
||||||
<DefaultButton onClick={() => setIsBarDismissed(true)}>Dimiss</DefaultButton>
|
|
||||||
</Stack>
|
|
||||||
</FocusZone>
|
|
||||||
</FocusTrapCallout>
|
|
||||||
) : undefined}
|
|
||||||
</ActionButton>
|
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</>
|
</ActionButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
import { Link } from "@fluentui/react";
|
|
||||||
import { CellId, CellType, ImmutableNotebook } from "@nteract/commutable";
|
import { CellId, CellType, ImmutableNotebook } from "@nteract/commutable";
|
||||||
// Vendor modules
|
// Vendor modules
|
||||||
import {
|
import {
|
||||||
@@ -15,15 +13,13 @@ import "@nteract/styles/editor-overrides.css";
|
|||||||
import "@nteract/styles/global-variables.css";
|
import "@nteract/styles/global-variables.css";
|
||||||
import "codemirror/addon/hint/show-hint.css";
|
import "codemirror/addon/hint/show-hint.css";
|
||||||
import "codemirror/lib/codemirror.css";
|
import "codemirror/lib/codemirror.css";
|
||||||
import { Notebook } from "Common/Constants";
|
|
||||||
import { useDialog } from "Explorer/Controls/Dialog";
|
|
||||||
import * as Immutable from "immutable";
|
import * as Immutable from "immutable";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import "react-table/react-table.css";
|
import "react-table/react-table.css";
|
||||||
import { AnyAction, Store } from "redux";
|
import { AnyAction, Store } from "redux";
|
||||||
import { NotebookClientV2 } from "../NotebookClientV2";
|
import { NotebookClientV2 } from "../NotebookClientV2";
|
||||||
import { NotebookContentProviderType, NotebookUtil } from "../NotebookUtil";
|
import { NotebookUtil } from "../NotebookUtil";
|
||||||
import * as NteractUtil from "../NTeractUtil";
|
import * as NteractUtil from "../NTeractUtil";
|
||||||
import * as CdbActions from "./actions";
|
import * as CdbActions from "./actions";
|
||||||
import { NotebookComponent } from "./NotebookComponent";
|
import { NotebookComponent } from "./NotebookComponent";
|
||||||
@@ -34,19 +30,6 @@ export interface NotebookComponentBootstrapperOptions {
|
|||||||
contentRef: ContentRef;
|
contentRef: ContentRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IWrapModel {
|
|
||||||
name: string;
|
|
||||||
path: string;
|
|
||||||
last_modified: Date;
|
|
||||||
created: string;
|
|
||||||
content: unknown;
|
|
||||||
format: string;
|
|
||||||
mimetype: unknown;
|
|
||||||
size: number;
|
|
||||||
writeable: boolean;
|
|
||||||
type: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NotebookComponentBootstrapper {
|
export class NotebookComponentBootstrapper {
|
||||||
public contentRef: ContentRef;
|
public contentRef: ContentRef;
|
||||||
protected renderExtraComponent: () => JSX.Element;
|
protected renderExtraComponent: () => JSX.Element;
|
||||||
@@ -58,7 +41,7 @@ export class NotebookComponentBootstrapper {
|
|||||||
this.contentRef = options.contentRef;
|
this.contentRef = options.contentRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static wrapModelIntoContent(name: string, path: string, content: unknown): IWrapModel {
|
protected static wrapModelIntoContent(name: string, path: string, content: any) {
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
path,
|
path,
|
||||||
@@ -66,7 +49,7 @@ export class NotebookComponentBootstrapper {
|
|||||||
created: "",
|
created: "",
|
||||||
content,
|
content,
|
||||||
format: "json",
|
format: "json",
|
||||||
mimetype: undefined,
|
mimetype: null as any,
|
||||||
size: 0,
|
size: 0,
|
||||||
writeable: false,
|
writeable: false,
|
||||||
type: "notebook",
|
type: "notebook",
|
||||||
@@ -102,11 +85,7 @@ export class NotebookComponentBootstrapper {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public getNotebookPath(): string {
|
public setContent(name: string, content: any): void {
|
||||||
return this.getStore().getState().core.entities.contents.byRef.get(this.contentRef)?.filepath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public setContent(name: string, content: unknown): void {
|
|
||||||
this.getStore().dispatch(
|
this.getStore().dispatch(
|
||||||
actions.fetchContentFulfilled({
|
actions.fetchContentFulfilled({
|
||||||
filepath: undefined,
|
filepath: undefined,
|
||||||
@@ -137,32 +116,11 @@ export class NotebookComponentBootstrapper {
|
|||||||
|
|
||||||
/* Notebook operations. See nteract/packages/connected-components/src/notebook-menu/index.tsx */
|
/* Notebook operations. See nteract/packages/connected-components/src/notebook-menu/index.tsx */
|
||||||
public notebookSave(): void {
|
public notebookSave(): void {
|
||||||
if (
|
this.getStore().dispatch(
|
||||||
NotebookUtil.getContentProviderType(this.getNotebookPath()) ===
|
actions.save({
|
||||||
NotebookContentProviderType.JupyterContentProviderType
|
contentRef: this.contentRef,
|
||||||
) {
|
})
|
||||||
useDialog.getState().showOkCancelModalDialog(
|
);
|
||||||
Notebook.saveNotebookModalTitle,
|
|
||||||
undefined,
|
|
||||||
"Save",
|
|
||||||
async () => {
|
|
||||||
this.getStore().dispatch(
|
|
||||||
actions.save({
|
|
||||||
contentRef: this.contentRef,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
"Cancel",
|
|
||||||
undefined,
|
|
||||||
this.getSaveNotebookSubText()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.getStore().dispatch(
|
|
||||||
actions.save({
|
|
||||||
contentRef: this.contentRef,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public notebookChangeKernel(kernelSpecName: string): void {
|
public notebookChangeKernel(kernelSpecName: string): void {
|
||||||
@@ -312,6 +270,7 @@ export class NotebookComponentBootstrapper {
|
|||||||
public isContentDirty(): boolean {
|
public isContentDirty(): boolean {
|
||||||
const content = selectors.content(this.getStore().getState(), { contentRef: this.contentRef });
|
const content = selectors.content(this.getStore().getState(), { contentRef: this.contentRef });
|
||||||
if (!content) {
|
if (!content) {
|
||||||
|
console.log("No error");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,19 +328,4 @@ export class NotebookComponentBootstrapper {
|
|||||||
protected getStore(): Store<AppState, AnyAction> {
|
protected getStore(): Store<AppState, AnyAction> {
|
||||||
return this.notebookClient.getStore();
|
return this.notebookClient.getStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSaveNotebookSubText(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<p>{Notebook.saveNotebookModalContent}</p>
|
|
||||||
<br />
|
|
||||||
<p>
|
|
||||||
{Notebook.newNotebookModalContent2}
|
|
||||||
<Link href={Notebook.cosmosNotebookHomePageUrl} target="_blank">
|
|
||||||
{Notebook.learnMore}
|
|
||||||
</Link>
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,11 @@ import {
|
|||||||
ServerConfig as JupyterServerConfig,
|
ServerConfig as JupyterServerConfig,
|
||||||
} from "@nteract/core";
|
} from "@nteract/core";
|
||||||
import { Channels, childOf, createMessage, JupyterMessage, message, ofMessageType } from "@nteract/messaging";
|
import { Channels, childOf, createMessage, JupyterMessage, message, ofMessageType } from "@nteract/messaging";
|
||||||
import { defineConfigOption } from "@nteract/mythic-configuration";
|
|
||||||
import { RecordOf } from "immutable";
|
import { RecordOf } from "immutable";
|
||||||
import { Action, AnyAction } from "redux";
|
import { AnyAction } from "redux";
|
||||||
import { ofType, StateObservable } from "redux-observable";
|
import { ofType, StateObservable } from "redux-observable";
|
||||||
import { kernels, sessions } from "rx-jupyter";
|
import { kernels, sessions } from "rx-jupyter";
|
||||||
import { concat, EMPTY, from, interval, merge, Observable, Observer, of, Subject, Subscriber, timer } from "rxjs";
|
import { concat, EMPTY, from, merge, Observable, Observer, of, Subject, Subscriber, timer } from "rxjs";
|
||||||
import {
|
import {
|
||||||
catchError,
|
catchError,
|
||||||
concatMap,
|
concatMap,
|
||||||
@@ -42,7 +41,7 @@ import { logConsoleError, logConsoleInfo } from "../../../Utils/NotificationCons
|
|||||||
import { useDialog } from "../../Controls/Dialog";
|
import { useDialog } from "../../Controls/Dialog";
|
||||||
import * as FileSystemUtil from "../FileSystemUtil";
|
import * as FileSystemUtil from "../FileSystemUtil";
|
||||||
import * as cdbActions from "../NotebookComponent/actions";
|
import * as cdbActions from "../NotebookComponent/actions";
|
||||||
import { NotebookContentProviderType, NotebookUtil } from "../NotebookUtil";
|
import { NotebookUtil } from "../NotebookUtil";
|
||||||
import * as CdbActions from "./actions";
|
import * as CdbActions from "./actions";
|
||||||
import * as TextFile from "./contents/file/text-file";
|
import * as TextFile from "./contents/file/text-file";
|
||||||
import { CdbAppState } from "./types";
|
import { CdbAppState } from "./types";
|
||||||
@@ -949,54 +948,6 @@ const resetCellStatusOnExecuteCanceledEpic = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { selector: autoSaveInterval } = defineConfigOption({
|
|
||||||
key: "autoSaveInterval",
|
|
||||||
label: "Auto-save interval",
|
|
||||||
defaultValue: 120_000,
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override autoSaveCurrentContentEpic to disable auto save for notebooks under temporary workspace.
|
|
||||||
* @param action$
|
|
||||||
*/
|
|
||||||
export function autoSaveCurrentContentEpic(
|
|
||||||
action$: Observable<Action>,
|
|
||||||
state$: StateObservable<AppState>
|
|
||||||
): Observable<actions.Save> {
|
|
||||||
return state$.pipe(
|
|
||||||
map((state) => autoSaveInterval(state)),
|
|
||||||
switchMap((time) => interval(time)),
|
|
||||||
mergeMap(() => {
|
|
||||||
const state = state$.value;
|
|
||||||
return from(
|
|
||||||
selectors
|
|
||||||
.contentByRef(state)
|
|
||||||
.filter(
|
|
||||||
/*
|
|
||||||
* Only save contents that are files or notebooks with
|
|
||||||
* a filepath already set.
|
|
||||||
*/
|
|
||||||
(content) => (content.type === "file" || content.type === "notebook") && content.filepath !== ""
|
|
||||||
)
|
|
||||||
.keys()
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
filter((contentRef: ContentRef) => {
|
|
||||||
const model = selectors.model(state$.value, { contentRef });
|
|
||||||
const content = selectors.content(state$.value, { contentRef });
|
|
||||||
if (
|
|
||||||
model &&
|
|
||||||
model.type === "notebook" &&
|
|
||||||
NotebookUtil.getContentProviderType(content.filepath) !== NotebookContentProviderType.JupyterContentProviderType
|
|
||||||
) {
|
|
||||||
return selectors.notebook.isDirty(model);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}),
|
|
||||||
map((contentRef: ContentRef) => actions.save({ contentRef }))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const allEpics = [
|
export const allEpics = [
|
||||||
addInitialCodeCellEpic,
|
addInitialCodeCellEpic,
|
||||||
focusInitialCodeCellEpic,
|
focusInitialCodeCellEpic,
|
||||||
@@ -1014,5 +965,4 @@ export const allEpics = [
|
|||||||
traceNotebookInfoEpic,
|
traceNotebookInfoEpic,
|
||||||
traceNotebookKernelEpic,
|
traceNotebookKernelEpic,
|
||||||
resetCellStatusOnExecuteCanceledEpic,
|
resetCellStatusOnExecuteCanceledEpic,
|
||||||
autoSaveCurrentContentEpic,
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { AppState, epics as coreEpics, IContentProvider, reducers } from "@nteract/core";
|
import { AppState, epics as coreEpics, reducers, IContentProvider } from "@nteract/core";
|
||||||
|
import { compose, Store, AnyAction, Middleware, Dispatch, MiddlewareAPI } from "redux";
|
||||||
|
import { Epic } from "redux-observable";
|
||||||
|
import { allEpics } from "./epics";
|
||||||
|
import { coreReducer, cdbReducer } from "./reducers";
|
||||||
|
import { catchError } from "rxjs/operators";
|
||||||
|
import { Observable } from "rxjs";
|
||||||
import { configuration } from "@nteract/mythic-configuration";
|
import { configuration } from "@nteract/mythic-configuration";
|
||||||
import { makeConfigureStore } from "@nteract/myths";
|
import { makeConfigureStore } from "@nteract/myths";
|
||||||
import { AnyAction, compose, Dispatch, Middleware, MiddlewareAPI, Store } from "redux";
|
|
||||||
import { Epic } from "redux-observable";
|
|
||||||
import { Observable } from "rxjs";
|
|
||||||
import { catchError } from "rxjs/operators";
|
|
||||||
import { allEpics } from "./epics";
|
|
||||||
import { cdbReducer, coreReducer } from "./reducers";
|
|
||||||
import { CdbAppState } from "./types";
|
import { CdbAppState } from "./types";
|
||||||
|
|
||||||
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||||
@@ -81,6 +81,7 @@ export const getCoreEpics = (autoStartKernelOnNotebookOpen: boolean): Epic[] =>
|
|||||||
// This list needs to be consistent and in sync with core.allEpics until we figure
|
// This list needs to be consistent and in sync with core.allEpics until we figure
|
||||||
// out how to safely filter out the ones we are overriding here.
|
// out how to safely filter out the ones we are overriding here.
|
||||||
const filteredCoreEpics = [
|
const filteredCoreEpics = [
|
||||||
|
coreEpics.autoSaveCurrentContentEpic,
|
||||||
coreEpics.executeCellEpic,
|
coreEpics.executeCellEpic,
|
||||||
coreEpics.executeFocusedCellEpic,
|
coreEpics.executeFocusedCellEpic,
|
||||||
coreEpics.executeCellAfterKernelLaunchEpic,
|
coreEpics.executeCellAfterKernelLaunchEpic,
|
||||||
|
|||||||
@@ -1,64 +1,50 @@
|
|||||||
/**
|
/**
|
||||||
* Notebook container related stuff
|
* Notebook container related stuff
|
||||||
*/
|
*/
|
||||||
import { useDialog } from "Explorer/Controls/Dialog";
|
|
||||||
import promiseRetry, { AbortError } from "p-retry";
|
|
||||||
import { PhoenixClient } from "Phoenix/PhoenixClient";
|
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { ConnectionStatusType, HttpHeaders, HttpStatusCodes, Notebook, PoolIdType } from "../../Common/Constants";
|
import { ConnectionStatusType } from "../../Common/Constants";
|
||||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||||
import * as Logger from "../../Common/Logger";
|
import * as Logger from "../../Common/Logger";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { IPhoenixConnectionInfoResult, IProvisionData, IResponse } from "../../Contracts/DataModels";
|
import { ContainerConnectionInfo } from "../../Contracts/DataModels";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
import { createOrUpdate, destroy } from "../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
||||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { NotebookUtil } from "./NotebookUtil";
|
||||||
import { useNotebook } from "./useNotebook";
|
import { useNotebook } from "./useNotebook";
|
||||||
|
|
||||||
export class NotebookContainerClient {
|
export class NotebookContainerClient {
|
||||||
private clearReconnectionAttemptMessage? = () => {};
|
private clearReconnectionAttemptMessage? = () => {};
|
||||||
private isResettingWorkspace: boolean;
|
private isResettingWorkspace: boolean;
|
||||||
private phoenixClient: PhoenixClient;
|
|
||||||
private retryOptions: promiseRetry.Options;
|
|
||||||
private scheduleTimerId: NodeJS.Timeout;
|
|
||||||
|
|
||||||
constructor(private onConnectionLost: () => void) {
|
constructor(private onConnectionLost: () => void) {
|
||||||
this.phoenixClient = new PhoenixClient();
|
|
||||||
this.retryOptions = {
|
|
||||||
retries: Notebook.retryAttempts,
|
|
||||||
maxTimeout: Notebook.retryAttemptDelayMs,
|
|
||||||
minTimeout: Notebook.retryAttemptDelayMs,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.initHeartbeat(Constants.Notebook.heartbeatDelayMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private initHeartbeat(delayMs: number): void {
|
|
||||||
this.scheduleHeartbeat(delayMs);
|
|
||||||
|
|
||||||
useNotebook.subscribe(
|
|
||||||
() => this.scheduleHeartbeat(delayMs),
|
|
||||||
(state) => state.notebookServerInfo
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private scheduleHeartbeat(delayMs: number) {
|
|
||||||
if (this.scheduleTimerId) {
|
|
||||||
clearInterval(this.scheduleTimerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||||
if (notebookServerInfo?.notebookServerEndpoint) {
|
if (notebookServerInfo?.notebookServerEndpoint) {
|
||||||
this.scheduleTimerId = setInterval(async () => {
|
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
|
||||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
} else {
|
||||||
if (notebookServerInfo?.notebookServerEndpoint) {
|
const unsub = useNotebook.subscribe(
|
||||||
const memoryUsageInfo = await this.getMemoryUsage();
|
(newServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => {
|
||||||
useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo);
|
if (newServerInfo?.notebookServerEndpoint) {
|
||||||
}
|
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
|
||||||
}, delayMs);
|
}
|
||||||
|
unsub();
|
||||||
|
},
|
||||||
|
(state) => state.notebookServerInfo
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Heartbeat: each ping schedules another ping
|
||||||
|
*/
|
||||||
|
private scheduleHeartbeat(delayMs: number): void {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.getMemoryUsage()
|
||||||
|
.then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo))
|
||||||
|
.finally(() => this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs));
|
||||||
|
}, delayMs);
|
||||||
|
}
|
||||||
|
|
||||||
public async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
|
public async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
|
||||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||||
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
||||||
@@ -73,27 +59,6 @@ export class NotebookContainerClient {
|
|||||||
|
|
||||||
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
||||||
try {
|
try {
|
||||||
const runMemoryAsync = async () => {
|
|
||||||
return await this._getMemoryAsync(notebookServerEndpoint, authToken);
|
|
||||||
};
|
|
||||||
return await promiseRetry(runMemoryAsync, this.retryOptions);
|
|
||||||
} catch (error) {
|
|
||||||
Logger.logError(getErrorMessage(error), "NotebookContainerClient/getMemoryUsage");
|
|
||||||
if (!this.clearReconnectionAttemptMessage) {
|
|
||||||
this.clearReconnectionAttemptMessage = logConsoleProgress(
|
|
||||||
"Connection lost with Notebook server. Attempting to reconnect..."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.onConnectionLost();
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _getMemoryAsync(
|
|
||||||
notebookServerEndpoint: string,
|
|
||||||
authToken: string
|
|
||||||
): Promise<DataModels.MemoryUsageInfo> {
|
|
||||||
if (this.shouldExecuteMemoryCall()) {
|
|
||||||
const response = await fetch(`${notebookServerEndpoint}api/metrics/memory`, {
|
const response = await fetch(`${notebookServerEndpoint}api/metrics/memory`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -113,36 +78,44 @@ export class NotebookContainerClient {
|
|||||||
freeKB: memoryUsageInfo.free,
|
freeKB: memoryUsageInfo.free,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else if (response.status === HttpStatusCodes.NotFound) {
|
} else if (NotebookUtil.isPhoenixEnabled()) {
|
||||||
throw new AbortError(response.statusText);
|
const connectionStatus: ContainerConnectionInfo = {
|
||||||
|
status: ConnectionStatusType.ReConnect,
|
||||||
|
};
|
||||||
|
useNotebook.getState().resetConatinerConnection(connectionStatus);
|
||||||
|
useNotebook.getState().setIsRefreshed(true);
|
||||||
}
|
}
|
||||||
throw new Error(response.statusText);
|
return undefined;
|
||||||
} else {
|
} catch (error) {
|
||||||
|
Logger.logError(getErrorMessage(error), "NotebookContainerClient/getMemoryUsage");
|
||||||
|
if (!this.clearReconnectionAttemptMessage) {
|
||||||
|
this.clearReconnectionAttemptMessage = logConsoleProgress(
|
||||||
|
"Connection lost with Notebook server. Attempting to reconnect..."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (NotebookUtil.isPhoenixEnabled()) {
|
||||||
|
const connectionStatus: ContainerConnectionInfo = {
|
||||||
|
status: ConnectionStatusType.Failed,
|
||||||
|
};
|
||||||
|
useNotebook.getState().resetConatinerConnection(connectionStatus);
|
||||||
|
useNotebook.getState().setIsRefreshed(true);
|
||||||
|
}
|
||||||
|
this.onConnectionLost();
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private shouldExecuteMemoryCall(): boolean {
|
public async resetWorkspace(): Promise<void> {
|
||||||
return (
|
|
||||||
useNotebook.getState().containerStatus?.status === Constants.ContainerStatusType.Active &&
|
|
||||||
useNotebook.getState().connectionInfo?.status === ConnectionStatusType.Connected
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async resetWorkspace(): Promise<IResponse<IPhoenixConnectionInfoResult>> {
|
|
||||||
this.isResettingWorkspace = true;
|
this.isResettingWorkspace = true;
|
||||||
let response: IResponse<IPhoenixConnectionInfoResult>;
|
|
||||||
try {
|
try {
|
||||||
response = await this._resetWorkspace();
|
await this._resetWorkspace();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Promise.reject(error);
|
Promise.reject(error);
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
this.isResettingWorkspace = false;
|
this.isResettingWorkspace = false;
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _resetWorkspace(): Promise<IResponse<IPhoenixConnectionInfoResult>> {
|
private async _resetWorkspace(): Promise<void> {
|
||||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||||
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
||||||
const error = "No server endpoint detected";
|
const error = "No server endpoint detected";
|
||||||
@@ -150,28 +123,15 @@ export class NotebookContainerClient {
|
|||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
||||||
try {
|
try {
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
await fetch(`${notebookServerEndpoint}/api/shutdown`, {
|
||||||
const provisionData: IProvisionData = {
|
method: "POST",
|
||||||
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
|
headers: { Authorization: authToken },
|
||||||
poolId: PoolIdType.DefaultPoolId,
|
});
|
||||||
};
|
|
||||||
return await this.phoenixClient.resetContainer(provisionData);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.logError(getErrorMessage(error), "NotebookContainerClient/resetWorkspace");
|
Logger.logError(getErrorMessage(error), "NotebookContainerClient/resetWorkspace");
|
||||||
if (error?.status === HttpStatusCodes.Forbidden && error.message) {
|
await this.recreateNotebookWorkspaceAsync();
|
||||||
useDialog.getState().showOkModalDialog("Connection Failed", `${error.message}`);
|
|
||||||
} else {
|
|
||||||
useDialog
|
|
||||||
.getState()
|
|
||||||
.showOkModalDialog(
|
|
||||||
"Connection Failed",
|
|
||||||
"We are unable to connect to the temporary workspace. Please try again in a few minutes. If the error persists, file a support ticket."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,11 +145,22 @@ export class NotebookContainerClient {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private getHeaders(): HeadersInit {
|
private async recreateNotebookWorkspaceAsync(): Promise<void> {
|
||||||
const authorizationHeader = getAuthorizationHeader();
|
const { databaseAccount } = userContext;
|
||||||
return {
|
if (!databaseAccount?.id) {
|
||||||
[authorizationHeader.header]: authorizationHeader.token,
|
throw new Error("DataExplorer not initialized");
|
||||||
[HttpHeaders.contentType]: "application/json",
|
}
|
||||||
};
|
try {
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -303,8 +303,8 @@ export class NotebookContentClient {
|
|||||||
private getServerConfig(): ServerConfig {
|
private getServerConfig(): ServerConfig {
|
||||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||||
return {
|
return {
|
||||||
endpoint: notebookServerInfo?.notebookServerEndpoint,
|
endpoint: notebookServerInfo.notebookServerEndpoint,
|
||||||
token: notebookServerInfo?.authToken,
|
token: notebookServerInfo.authToken,
|
||||||
crossDomain: true,
|
crossDomain: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,9 @@ import "./NotebookReadOnlyRenderer.less";
|
|||||||
import SandboxOutputs from "./outputs/SandboxOutputs";
|
import SandboxOutputs from "./outputs/SandboxOutputs";
|
||||||
|
|
||||||
export interface NotebookRendererProps {
|
export interface NotebookRendererProps {
|
||||||
contentRef: ContentRef;
|
contentRef: any;
|
||||||
hideInputs?: boolean;
|
hideInputs?: boolean;
|
||||||
hidePrompts?: boolean;
|
hidePrompts?: boolean;
|
||||||
addTransform: (component: React.ComponentType & { MIMETYPE: string }) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,7 +27,7 @@ export interface NotebookRendererProps {
|
|||||||
class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (!userContext.features.sandboxNotebookOutputs) {
|
if (!userContext.features.sandboxNotebookOutputs) {
|
||||||
loadTransform(this.props as NotebookRendererProps);
|
loadTransform(this.props as any);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
|||||||
<div className="NotebookReadOnlyRender">
|
<div className="NotebookReadOnlyRender">
|
||||||
<Cells contentRef={this.props.contentRef}>
|
<Cells contentRef={this.props.contentRef}>
|
||||||
{{
|
{{
|
||||||
code: ({ id, contentRef }: { id: string; contentRef: ContentRef }) => (
|
code: ({ id, contentRef }: { id: any; contentRef: ContentRef }) => (
|
||||||
<CodeCell id={id} contentRef={contentRef}>
|
<CodeCell id={id} contentRef={contentRef}>
|
||||||
{{
|
{{
|
||||||
prompt: (props: { id: string; contentRef: string }) => this.renderPrompt(props.id, props.contentRef),
|
prompt: (props: { id: string; contentRef: string }) => this.renderPrompt(props.id, props.contentRef),
|
||||||
@@ -74,14 +73,14 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
|||||||
}}
|
}}
|
||||||
</CodeCell>
|
</CodeCell>
|
||||||
),
|
),
|
||||||
markdown: ({ id, contentRef }: { id: string; contentRef: ContentRef }) => (
|
markdown: ({ id, contentRef }: { id: any; contentRef: ContentRef }) => (
|
||||||
<MarkdownCell id={id} contentRef={contentRef} cell_type="markdown">
|
<MarkdownCell id={id} contentRef={contentRef} cell_type="markdown">
|
||||||
{{
|
{{
|
||||||
editor: {},
|
editor: {},
|
||||||
}}
|
}}
|
||||||
</MarkdownCell>
|
</MarkdownCell>
|
||||||
),
|
),
|
||||||
raw: ({ id, contentRef }: { id: string; contentRef: ContentRef }) => (
|
raw: ({ id, contentRef }: { id: any; contentRef: ContentRef }) => (
|
||||||
<RawCell id={id} contentRef={contentRef} cell_type="raw">
|
<RawCell id={id} contentRef={contentRef} cell_type="raw">
|
||||||
{{
|
{{
|
||||||
editor: {
|
editor: {
|
||||||
@@ -99,7 +98,6 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: NotebookRendererProps) => {
|
const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: NotebookRendererProps) => {
|
||||||
const mapDispatchToProps = (dispatch: Dispatch) => {
|
const mapDispatchToProps = (dispatch: Dispatch) => {
|
||||||
return {
|
return {
|
||||||
@@ -116,4 +114,4 @@ const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: Noteboo
|
|||||||
return mapDispatchToProps;
|
return mapDispatchToProps;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(undefined, makeMapDispatchToProps)(NotebookReadOnlyRenderer);
|
export default connect(null, makeMapDispatchToProps)(NotebookReadOnlyRenderer);
|
||||||
|
|||||||
@@ -31,12 +31,13 @@ import StatusBar from "./StatusBar";
|
|||||||
import CellToolbar from "./Toolbar";
|
import CellToolbar from "./Toolbar";
|
||||||
|
|
||||||
export interface NotebookRendererBaseProps {
|
export interface NotebookRendererBaseProps {
|
||||||
contentRef: any;
|
contentRef: ContentRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NotebookRendererDispatchProps {
|
interface NotebookRendererDispatchProps {
|
||||||
storeNotebookSnapshot: (imageSrc: string, requestId: string) => void;
|
storeNotebookSnapshot: (imageSrc: string, requestId: string) => void;
|
||||||
notebookSnapshotError: (error: string) => void;
|
notebookSnapshotError: (error: string) => void;
|
||||||
|
addTransform: (transform: React.ComponentType & { MIMETYPE: string }) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StateProps {
|
interface StateProps {
|
||||||
@@ -73,7 +74,7 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (!userContext.features.sandboxNotebookOutputs) {
|
if (!userContext.features.sandboxNotebookOutputs) {
|
||||||
loadTransform(this.props as any);
|
loadTransform(this.props as NotebookRendererProps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +139,7 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
|
|||||||
}}
|
}}
|
||||||
</CodeCell>
|
</CodeCell>
|
||||||
),
|
),
|
||||||
markdown: ({ id, contentRef }: { id: any; contentRef: ContentRef }) =>
|
markdown: ({ id, contentRef }: { id: CellId; contentRef: ContentRef }) =>
|
||||||
decorate(
|
decorate(
|
||||||
id,
|
id,
|
||||||
contentRef,
|
contentRef,
|
||||||
@@ -155,7 +156,7 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
|
|||||||
</MarkdownCell>
|
</MarkdownCell>
|
||||||
),
|
),
|
||||||
|
|
||||||
raw: ({ id, contentRef }: { id: any; contentRef: ContentRef }) =>
|
raw: ({ id, contentRef }: { id: CellId; contentRef: ContentRef }) =>
|
||||||
decorate(
|
decorate(
|
||||||
id,
|
id,
|
||||||
contentRef,
|
contentRef,
|
||||||
@@ -202,7 +203,8 @@ export const makeMapStateToProps = (
|
|||||||
return mapStateToProps;
|
return mapStateToProps;
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: NotebookRendererBaseProps) => {
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const makeMapDispatchToProps = (_initialDispatch: Dispatch, _initialProps: NotebookRendererBaseProps) => {
|
||||||
const mapDispatchToProps = (dispatch: Dispatch) => {
|
const mapDispatchToProps = (dispatch: Dispatch) => {
|
||||||
return {
|
return {
|
||||||
addTransform: (transform: React.ComponentType & { MIMETYPE: string }) =>
|
addTransform: (transform: React.ComponentType & { MIMETYPE: string }) =>
|
||||||
|
|||||||
@@ -3,19 +3,14 @@ import { AppState, selectors } from "@nteract/core";
|
|||||||
import domtoimage from "dom-to-image";
|
import domtoimage from "dom-to-image";
|
||||||
import Html2Canvas from "html2canvas";
|
import Html2Canvas from "html2canvas";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||||
import * as StringUtils from "../../Utils/StringUtils";
|
import * as StringUtils from "../../Utils/StringUtils";
|
||||||
import * as InMemoryContentProviderUtils from "../Notebook/NotebookComponent/ContentProviders/InMemoryContentProviderUtils";
|
|
||||||
import { SnapshotFragment } from "./NotebookComponent/types";
|
import { SnapshotFragment } from "./NotebookComponent/types";
|
||||||
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
||||||
|
|
||||||
// Must match rx-jupyter' FileType
|
// Must match rx-jupyter' FileType
|
||||||
export type FileType = "directory" | "file" | "notebook";
|
export type FileType = "directory" | "file" | "notebook";
|
||||||
export enum NotebookContentProviderType {
|
|
||||||
GitHubContentProviderType,
|
|
||||||
InMemoryContentProviderType,
|
|
||||||
JupyterContentProviderType,
|
|
||||||
}
|
|
||||||
// Utilities for notebooks
|
// Utilities for notebooks
|
||||||
export class NotebookUtil {
|
export class NotebookUtil {
|
||||||
public static UntrustedNotebookRunHint = "Please trust notebook first before running any code cells";
|
public static UntrustedNotebookRunHint = "Please trust notebook first before running any code cells";
|
||||||
@@ -132,18 +127,6 @@ export class NotebookUtil {
|
|||||||
return relativePath.split("/").pop();
|
return relativePath.split("/").pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getContentProviderType(path: string): NotebookContentProviderType {
|
|
||||||
if (InMemoryContentProviderUtils.fromContentUri(path)) {
|
|
||||||
return NotebookContentProviderType.InMemoryContentProviderType;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GitHubUtils.fromContentUri(path)) {
|
|
||||||
return NotebookContentProviderType.GitHubContentProviderType;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NotebookContentProviderType.JupyterContentProviderType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static replaceName(path: string, newName: string): string {
|
public static replaceName(path: string, newName: string): string {
|
||||||
const contentInfo = GitHubUtils.fromContentUri(path);
|
const contentInfo = GitHubUtils.fromContentUri(path);
|
||||||
if (contentInfo) {
|
if (contentInfo) {
|
||||||
@@ -346,4 +329,16 @@ export class NotebookUtil {
|
|||||||
link.click();
|
link.click();
|
||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static getNotebookBtnTitle(fileName: string): string {
|
||||||
|
if (this.isPhoenixEnabled()) {
|
||||||
|
return `Download to ${fileName}`;
|
||||||
|
} else {
|
||||||
|
return `Download to my notebooks`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static isPhoenixEnabled(): boolean {
|
||||||
|
return userContext.features.notebooksTemporarilyDown === false && userContext.features.phoenix === true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import Immutable from "immutable";
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Dispatch } from "redux";
|
import { Dispatch } from "redux";
|
||||||
import * as Logger from "../../../Common/Logger";
|
|
||||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import loadTransform from "../NotebookComponent/loadTransform";
|
import loadTransform from "../NotebookComponent/loadTransform";
|
||||||
@@ -101,7 +100,6 @@ export class SchemaAnalyzer extends React.Component<SchemaAnalyzerProps, SchemaA
|
|||||||
// Only in cases where CosmosMongoKernel runs into an error we get a single output
|
// Only in cases where CosmosMongoKernel runs into an error we get a single output
|
||||||
if (outputs.size === 1) {
|
if (outputs.size === 1) {
|
||||||
traceFailure(Action.SchemaAnalyzerClickAnalyze, data, this.clickAnalyzeTelemetryStartKey);
|
traceFailure(Action.SchemaAnalyzerClickAnalyze, data, this.clickAnalyzeTelemetryStartKey);
|
||||||
Logger.logError(`Failed to analyze schema: ${JSON.stringify(data)}`, "SchemaAnalyzer/traceClickAnalyzeComplete");
|
|
||||||
} else {
|
} else {
|
||||||
traceSuccess(Action.SchemaAnalyzerClickAnalyze, data, this.clickAnalyzeTelemetryStartKey);
|
traceSuccess(Action.SchemaAnalyzerClickAnalyze, data, this.clickAnalyzeTelemetryStartKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ describe("auto start kernel", () => {
|
|||||||
connectionInfo: {
|
connectionInfo: {
|
||||||
authToken: "autToken",
|
authToken: "autToken",
|
||||||
notebookServerEndpoint: "notebookServerEndpoint",
|
notebookServerEndpoint: "notebookServerEndpoint",
|
||||||
forwardingId: "Id",
|
|
||||||
},
|
},
|
||||||
databaseAccountName: undefined,
|
databaseAccountName: undefined,
|
||||||
defaultExperience: undefined,
|
defaultExperience: undefined,
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { PhoenixClient } from "Phoenix/PhoenixClient";
|
|
||||||
import create, { UseStore } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { ConnectionStatusType, HttpStatusCodes } from "../../Common/Constants";
|
import { ConnectionStatusType } from "../../Common/Constants";
|
||||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||||
import * as Logger from "../../Common/Logger";
|
import * as Logger from "../../Common/Logger";
|
||||||
import { configContext } from "../../ConfigContext";
|
import { configContext } from "../../ConfigContext";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { ContainerConnectionInfo, ContainerInfo, PhoenixErrorType } from "../../Contracts/DataModels";
|
import { ContainerConnectionInfo } from "../../Contracts/DataModels";
|
||||||
import { useTabs } from "../../hooks/useTabs";
|
|
||||||
import { IPinnedRepo } from "../../Juno/JunoClient";
|
import { IPinnedRepo } from "../../Juno/JunoClient";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
@@ -19,6 +16,7 @@ import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
|||||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||||
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
||||||
import NotebookManager from "./NotebookManager";
|
import NotebookManager from "./NotebookManager";
|
||||||
|
import { NotebookUtil } from "./NotebookUtil";
|
||||||
|
|
||||||
interface NotebookState {
|
interface NotebookState {
|
||||||
isNotebookEnabled: boolean;
|
isNotebookEnabled: boolean;
|
||||||
@@ -37,9 +35,6 @@ interface NotebookState {
|
|||||||
notebookFolderName: string;
|
notebookFolderName: string;
|
||||||
isAllocating: boolean;
|
isAllocating: boolean;
|
||||||
isRefreshed: boolean;
|
isRefreshed: boolean;
|
||||||
containerStatus: ContainerInfo;
|
|
||||||
isPhoenixNotebooks: boolean;
|
|
||||||
isPhoenixFeatures: boolean;
|
|
||||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
|
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
|
||||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
|
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
|
||||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
|
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
|
||||||
@@ -58,12 +53,8 @@ interface NotebookState {
|
|||||||
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
|
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
|
||||||
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => void;
|
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => void;
|
||||||
setIsAllocating: (isAllocating: boolean) => void;
|
setIsAllocating: (isAllocating: boolean) => void;
|
||||||
resetContainerConnection: (connectionStatus: ContainerConnectionInfo) => void;
|
resetConatinerConnection: (connectionStatus: ContainerConnectionInfo) => void;
|
||||||
setIsRefreshed: (isAllocating: boolean) => void;
|
setIsRefreshed: (isAllocating: boolean) => void;
|
||||||
setContainerStatus: (containerStatus: ContainerInfo) => void;
|
|
||||||
getPhoenixStatus: () => Promise<void>;
|
|
||||||
setIsPhoenixNotebooks: (isPhoenixNotebooks: boolean) => void;
|
|
||||||
setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||||
@@ -72,7 +63,6 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
notebookServerInfo: {
|
notebookServerInfo: {
|
||||||
notebookServerEndpoint: undefined,
|
notebookServerEndpoint: undefined,
|
||||||
authToken: undefined,
|
authToken: undefined,
|
||||||
forwardingId: undefined,
|
|
||||||
},
|
},
|
||||||
sparkClusterConnectionInfo: {
|
sparkClusterConnectionInfo: {
|
||||||
userName: undefined,
|
userName: undefined,
|
||||||
@@ -93,13 +83,6 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
notebookFolderName: undefined,
|
notebookFolderName: undefined,
|
||||||
isAllocating: false,
|
isAllocating: false,
|
||||||
isRefreshed: false,
|
isRefreshed: false,
|
||||||
containerStatus: {
|
|
||||||
status: undefined,
|
|
||||||
durationLeftInMinutes: undefined,
|
|
||||||
phoenixServerInfo: undefined,
|
|
||||||
},
|
|
||||||
isPhoenixNotebooks: undefined,
|
|
||||||
isPhoenixFeatures: undefined,
|
|
||||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
||||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
||||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
||||||
@@ -112,7 +95,6 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }),
|
setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }),
|
||||||
setNotebookFolderName: (notebookFolderName: string) => set({ notebookFolderName }),
|
setNotebookFolderName: (notebookFolderName: string) => set({ notebookFolderName }),
|
||||||
refreshNotebooksEnabledStateForAccount: async (): Promise<void> => {
|
refreshNotebooksEnabledStateForAccount: async (): Promise<void> => {
|
||||||
await get().getPhoenixStatus();
|
|
||||||
const { databaseAccount, authType } = userContext;
|
const { databaseAccount, authType } = userContext;
|
||||||
if (
|
if (
|
||||||
authType === AuthType.EncryptedToken ||
|
authType === AuthType.EncryptedToken ||
|
||||||
@@ -205,7 +187,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
||||||
},
|
},
|
||||||
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
||||||
const notebookFolderName = get().isPhoenixNotebooks ? "Temporary Notebooks" : "My Notebooks";
|
const notebookFolderName = NotebookUtil.isPhoenixEnabled() === true ? "Temporary Notebooks" : "My Notebooks";
|
||||||
set({ notebookFolderName });
|
set({ notebookFolderName });
|
||||||
const myNotebooksContentRoot = {
|
const myNotebooksContentRoot = {
|
||||||
name: get().notebookFolderName,
|
name: get().notebookFolderName,
|
||||||
@@ -288,42 +270,13 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
},
|
},
|
||||||
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => set({ connectionInfo }),
|
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => set({ connectionInfo }),
|
||||||
setIsAllocating: (isAllocating: boolean) => set({ isAllocating }),
|
setIsAllocating: (isAllocating: boolean) => set({ isAllocating }),
|
||||||
resetContainerConnection: (connectionStatus: ContainerConnectionInfo): void => {
|
resetConatinerConnection: (connectionStatus: ContainerConnectionInfo): void => {
|
||||||
useTabs.getState().closeAllNotebookTabs(true);
|
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||||
useNotebook.getState().setNotebookServerInfo(undefined);
|
useNotebook.getState().setNotebookServerInfo({
|
||||||
useNotebook.getState().setIsAllocating(false);
|
notebookServerEndpoint: undefined,
|
||||||
useNotebook.getState().setContainerStatus({
|
authToken: undefined,
|
||||||
status: undefined,
|
|
||||||
durationLeftInMinutes: undefined,
|
|
||||||
phoenixServerInfo: undefined,
|
|
||||||
});
|
});
|
||||||
|
useNotebook.getState().setIsAllocating(false);
|
||||||
},
|
},
|
||||||
setIsRefreshed: (isRefreshed: boolean) => set({ isRefreshed }),
|
setIsRefreshed: (isRefreshed: boolean) => set({ isRefreshed }),
|
||||||
setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }),
|
|
||||||
getPhoenixStatus: async () => {
|
|
||||||
if (get().isPhoenixNotebooks === undefined || get().isPhoenixFeatures === undefined) {
|
|
||||||
let isPhoenixNotebooks = false;
|
|
||||||
let isPhoenixFeatures = false;
|
|
||||||
|
|
||||||
const isPublicInternetAllowed = isPublicInternetAccessAllowed();
|
|
||||||
const phoenixClient = new PhoenixClient();
|
|
||||||
const dbAccountAllowedInfo = await phoenixClient.getDbAccountAllowedStatus();
|
|
||||||
|
|
||||||
if (dbAccountAllowedInfo.status === HttpStatusCodes.OK) {
|
|
||||||
if (dbAccountAllowedInfo?.type === PhoenixErrorType.PhoenixFlightFallback) {
|
|
||||||
isPhoenixNotebooks = isPublicInternetAllowed && userContext.features.phoenixNotebooks;
|
|
||||||
isPhoenixFeatures = isPublicInternetAllowed && userContext.features.phoenixFeatures;
|
|
||||||
} else {
|
|
||||||
isPhoenixNotebooks = isPhoenixFeatures = isPublicInternetAllowed;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
isPhoenixNotebooks = isPhoenixFeatures = false;
|
|
||||||
}
|
|
||||||
set({ isPhoenixNotebooks: isPhoenixNotebooks });
|
|
||||||
set({ isPhoenixFeatures: isPhoenixFeatures });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setIsPhoenixNotebooks: (isPhoenixNotebooks: boolean) => set({ isPhoenixNotebooks: isPhoenixNotebooks }),
|
|
||||||
setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => set({ isPhoenixFeatures: isPhoenixFeatures }),
|
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -8,23 +8,23 @@ import { CassandraAddCollectionPane } from "../Panes/CassandraAddCollectionPane/
|
|||||||
import { SettingsPane } from "../Panes/SettingsPane/SettingsPane";
|
import { SettingsPane } from "../Panes/SettingsPane/SettingsPane";
|
||||||
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
|
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
|
||||||
|
|
||||||
function generateQueryText(action: ActionContracts.OpenQueryTab, partitionKeyProperties: string[]): string {
|
function generateQueryText(action: ActionContracts.OpenQueryTab, partitionKeyProperty: string): string {
|
||||||
if (!action.query) {
|
if (!action.query) {
|
||||||
return "SELECT * FROM c";
|
return "SELECT * FROM c";
|
||||||
} else if (action.query.text) {
|
} else if (action.query.text) {
|
||||||
return action.query.text;
|
return action.query.text;
|
||||||
} else if (action.query.partitionKeys?.length > 0 && partitionKeyProperties?.length > 0) {
|
} else if (!!action.query.partitionKeys && action.query.partitionKeys.length > 0) {
|
||||||
let query = "SELECT * FROM c WHERE";
|
let query = "SELECT * FROM c WHERE";
|
||||||
for (let i = 0; i < action.query.partitionKeys.length; i++) {
|
for (let i = 0; i < action.query.partitionKeys.length; i++) {
|
||||||
const partitionKey = action.query.partitionKeys[i];
|
const partitionKey = action.query.partitionKeys[i];
|
||||||
if (!partitionKey) {
|
if (!partitionKey) {
|
||||||
// null partition key case
|
// null partition key case
|
||||||
query = query.concat(` c.${partitionKeyProperties[i]} = ${action.query.partitionKeys[i]}`);
|
query = query.concat(` c.${partitionKeyProperty} = ${action.query.partitionKeys[i]}`);
|
||||||
} else if (typeof partitionKey !== "string") {
|
} else if (typeof partitionKey !== "string") {
|
||||||
// Undefined partition key case
|
// Undefined partition key case
|
||||||
query = query.concat(` NOT IS_DEFINED(c.${partitionKeyProperties[i]})`);
|
query = query.concat(` NOT IS_DEFINED(c.${partitionKeyProperty})`);
|
||||||
} else {
|
} else {
|
||||||
query = query.concat(` c.${partitionKeyProperties[i]} = "${action.query.partitionKeys[i]}"`);
|
query = query.concat(` c.${partitionKeyProperty} = "${action.query.partitionKeys[i]}"`);
|
||||||
}
|
}
|
||||||
if (i !== action.query.partitionKeys.length - 1) {
|
if (i !== action.query.partitionKeys.length - 1) {
|
||||||
query = query.concat(" OR");
|
query = query.concat(" OR");
|
||||||
@@ -109,7 +109,7 @@ function openCollectionTab(
|
|||||||
collection.onNewQueryClick(
|
collection.onNewQueryClick(
|
||||||
collection,
|
collection,
|
||||||
undefined,
|
undefined,
|
||||||
generateQueryText(action as ActionContracts.OpenQueryTab, collection.partitionKeyProperties)
|
generateQueryText(action as ActionContracts.OpenQueryTab, collection.partitionKeyProperty)
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import * as React from "react";
|
|||||||
import { useFullScreenURLs } from "../hooks/useFullScreenURLs";
|
import { useFullScreenURLs } from "../hooks/useFullScreenURLs";
|
||||||
|
|
||||||
export const OpenFullScreen: React.FunctionComponent = () => {
|
export const OpenFullScreen: React.FunctionComponent = () => {
|
||||||
const [isReadUrlCopy, setIsReadUrlCopy] = React.useState<boolean>(false);
|
|
||||||
const [isReadWriteUrlCopy, setIsReadWriteUrlCopy] = React.useState<boolean>(false);
|
|
||||||
const result = useFullScreenURLs();
|
const result = useFullScreenURLs();
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return <Spinner label="Generating URLs..." ariaLive="assertive" labelPosition="right" />;
|
return <Spinner label="Generating URLs..." ariaLive="assertive" labelPosition="right" />;
|
||||||
@@ -25,12 +23,10 @@ export const OpenFullScreen: React.FunctionComponent = () => {
|
|||||||
<TextField label="Read and Write" readOnly defaultValue={readWriteUrl} />
|
<TextField label="Read and Write" readOnly defaultValue={readWriteUrl} />
|
||||||
<Stack horizontal tokens={{ childrenGap: 10 }}>
|
<Stack horizontal tokens={{ childrenGap: 10 }}>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
ariaLabel={isReadWriteUrlCopy ? "Copied url" : "Copy"}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
copyToClipboard(readWriteUrl);
|
copyToClipboard(readWriteUrl);
|
||||||
setIsReadWriteUrlCopy(true);
|
|
||||||
}}
|
}}
|
||||||
text={isReadWriteUrlCopy ? "Copied" : "Copy"}
|
text="Copy"
|
||||||
iconProps={{ iconName: "Copy" }}
|
iconProps={{ iconName: "Copy" }}
|
||||||
/>
|
/>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
@@ -44,12 +40,10 @@ export const OpenFullScreen: React.FunctionComponent = () => {
|
|||||||
<TextField label="Read Only" readOnly defaultValue={readUrl} />
|
<TextField label="Read Only" readOnly defaultValue={readUrl} />
|
||||||
<Stack horizontal tokens={{ childrenGap: 10 }}>
|
<Stack horizontal tokens={{ childrenGap: 10 }}>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
ariaLabel={isReadUrlCopy ? "Copied url" : "Copy"}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsReadUrlCopy(true);
|
|
||||||
copyToClipboard(readUrl);
|
copyToClipboard(readUrl);
|
||||||
}}
|
}}
|
||||||
text={isReadUrlCopy ? "Copied" : "Copy"}
|
text="Copy"
|
||||||
iconProps={{ iconName: "Copy" }}
|
iconProps={{ iconName: "Copy" }}
|
||||||
/>
|
/>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import { shallow } from "enzyme";
|
|
||||||
import React from "react";
|
|
||||||
import Explorer from "../Explorer";
|
|
||||||
import { AddCollectionPanel } from "./AddCollectionPanel";
|
|
||||||
|
|
||||||
const props = {
|
|
||||||
explorer: new Explorer(),
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("AddCollectionPanel", () => {
|
|
||||||
it("should render Default properly", () => {
|
|
||||||
const wrapper = shallow(<AddCollectionPanel {...props} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -8,32 +8,28 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
IDropdownOption,
|
IDropdownOption,
|
||||||
Link,
|
Link,
|
||||||
ProgressIndicator,
|
|
||||||
Separator,
|
Separator,
|
||||||
Stack,
|
Stack,
|
||||||
TeachingBubble,
|
|
||||||
Text,
|
Text,
|
||||||
TooltipHost,
|
TooltipHost,
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import * as Constants from "Common/Constants";
|
|
||||||
import { createCollection } from "Common/dataAccess/createCollection";
|
|
||||||
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
|
||||||
import { configContext, Platform } from "ConfigContext";
|
|
||||||
import * as DataModels from "Contracts/DataModels";
|
|
||||||
import { SubscriptionType } from "Contracts/SubscriptionType";
|
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
|
||||||
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { CollectionCreation } from "Shared/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
import { createCollection } from "../../Common/dataAccess/createCollection";
|
||||||
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
import { userContext } from "UserContext";
|
import { configContext, Platform } from "../../ConfigContext";
|
||||||
import { getCollectionName } from "Utils/APITypeUtils";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { isCapabilityEnabled, isServerlessAccount } from "Utils/CapabilityUtils";
|
import { SubscriptionType } from "../../Contracts/SubscriptionType";
|
||||||
import { getUpsellMessage } from "Utils/PricingUtils";
|
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||||
|
import { CollectionCreation } from "../../Shared/Constants";
|
||||||
|
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
import { getCollectionName } from "../../Utils/APITypeUtils";
|
||||||
|
import { isCapabilityEnabled, isServerlessAccount } from "../../Utils/CapabilityUtils";
|
||||||
|
import { getUpsellMessage } from "../../Utils/PricingUtils";
|
||||||
import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
||||||
import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput";
|
import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput";
|
||||||
import { ContainerSampleGenerator } from "../DataSamples/ContainerSampleGenerator";
|
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { useDatabases } from "../useDatabases";
|
import { useDatabases } from "../useDatabases";
|
||||||
import { PanelFooterComponent } from "./PanelFooterComponent";
|
import { PanelFooterComponent } from "./PanelFooterComponent";
|
||||||
@@ -43,7 +39,6 @@ import { PanelLoadingScreen } from "./PanelLoadingScreen";
|
|||||||
export interface AddCollectionPanelProps {
|
export interface AddCollectionPanelProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
databaseId?: string;
|
databaseId?: string;
|
||||||
isQuickstart?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SharedDatabaseDefault: DataModels.IndexingPolicy = {
|
const SharedDatabaseDefault: DataModels.IndexingPolicy = {
|
||||||
@@ -97,8 +92,6 @@ export interface AddCollectionPanelState {
|
|||||||
errorMessage: string;
|
errorMessage: string;
|
||||||
showErrorDetails: boolean;
|
showErrorDetails: boolean;
|
||||||
isExecuting: boolean;
|
isExecuting: boolean;
|
||||||
isThroughputCapExceeded: boolean;
|
|
||||||
teachingBubbleStep: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AddCollectionPanel extends React.Component<AddCollectionPanelProps, AddCollectionPanelState> {
|
export class AddCollectionPanel extends React.Component<AddCollectionPanelProps, AddCollectionPanelState> {
|
||||||
@@ -113,11 +106,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
createNewDatabase: userContext.apiType !== "Tables" && !this.props.databaseId,
|
createNewDatabase: userContext.apiType !== "Tables" && !this.props.databaseId,
|
||||||
newDatabaseId: props.isQuickstart ? this.getSampleDBName() : "",
|
newDatabaseId: "",
|
||||||
isSharedThroughputChecked: this.getSharedThroughputDefault(),
|
isSharedThroughputChecked: this.getSharedThroughputDefault(),
|
||||||
selectedDatabaseId:
|
selectedDatabaseId:
|
||||||
userContext.apiType === "Tables" ? CollectionCreation.TablesAPIDefaultDatabase : this.props.databaseId,
|
userContext.apiType === "Tables" ? CollectionCreation.TablesAPIDefaultDatabase : this.props.databaseId,
|
||||||
collectionId: props.isQuickstart ? `Sample${getCollectionName()}` : "",
|
collectionId: "",
|
||||||
enableIndexing: true,
|
enableIndexing: true,
|
||||||
isSharded: userContext.apiType !== "Tables",
|
isSharded: userContext.apiType !== "Tables",
|
||||||
partitionKey: this.getPartitionKey(),
|
partitionKey: this.getPartitionKey(),
|
||||||
@@ -129,17 +122,9 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
errorMessage: "",
|
errorMessage: "",
|
||||||
showErrorDetails: false,
|
showErrorDetails: false,
|
||||||
isExecuting: false,
|
isExecuting: false,
|
||||||
isThroughputCapExceeded: false,
|
|
||||||
teachingBubbleStep: 0,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
|
||||||
if (this.state.teachingBubbleStep === 0 && this.props.isQuickstart) {
|
|
||||||
this.setState({ teachingBubbleStep: 1 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
const isFirstResourceCreated = useDatabases.getState().isFirstResourceCreated();
|
const isFirstResourceCreated = useDatabases.getState().isFirstResourceCreated();
|
||||||
|
|
||||||
@@ -163,89 +148,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.state.teachingBubbleStep === 1 && (
|
|
||||||
<TeachingBubble
|
|
||||||
headline="Create sample database"
|
|
||||||
target={"#newDatabaseId"}
|
|
||||||
calloutProps={{ gapSpace: 16 }}
|
|
||||||
primaryButtonProps={{ text: "Next", onClick: () => this.setState({ teachingBubbleStep: 2 }) }}
|
|
||||||
secondaryButtonProps={{ text: "Cancel", onClick: () => this.setState({ teachingBubbleStep: 0 }) }}
|
|
||||||
onDismiss={() => this.setState({ teachingBubbleStep: 0 })}
|
|
||||||
footerContent="Step 1 of 4"
|
|
||||||
>
|
|
||||||
<Stack>
|
|
||||||
<Text style={{ color: "white" }}>
|
|
||||||
Database is the parent of a container. You can create a new database or use an existing one. In this
|
|
||||||
tutorial we are creating a new database named SampleDB.
|
|
||||||
</Text>
|
|
||||||
<Link
|
|
||||||
style={{ color: "white", fontWeight: 600 }}
|
|
||||||
target="_blank"
|
|
||||||
href="https://aka.ms/TeachingbubbleResources"
|
|
||||||
>
|
|
||||||
Learn more about resources.
|
|
||||||
</Link>
|
|
||||||
</Stack>
|
|
||||||
</TeachingBubble>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{this.state.teachingBubbleStep === 2 && (
|
|
||||||
<TeachingBubble
|
|
||||||
headline="Setting throughput"
|
|
||||||
target={"#autoscaleRUValueField"}
|
|
||||||
calloutProps={{ gapSpace: 16 }}
|
|
||||||
primaryButtonProps={{ text: "Next", onClick: () => this.setState({ teachingBubbleStep: 3 }) }}
|
|
||||||
secondaryButtonProps={{ text: "Previous", onClick: () => this.setState({ teachingBubbleStep: 1 }) }}
|
|
||||||
onDismiss={() => this.setState({ teachingBubbleStep: 0 })}
|
|
||||||
footerContent="Step 2 of 4"
|
|
||||||
>
|
|
||||||
<Stack>
|
|
||||||
<Text style={{ color: "white" }}>
|
|
||||||
Cosmos DB recommends sharing throughput across database. Autoscale will give you a flexible amount of
|
|
||||||
throughput based on the max RU/s set (Request Units).
|
|
||||||
</Text>
|
|
||||||
<Link style={{ color: "white", fontWeight: 600 }} target="_blank" href="https://aka.ms/teachingbubbleRU">
|
|
||||||
Learn more about RU/s.
|
|
||||||
</Link>
|
|
||||||
</Stack>
|
|
||||||
</TeachingBubble>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{this.state.teachingBubbleStep === 3 && (
|
|
||||||
<TeachingBubble
|
|
||||||
headline="Naming container"
|
|
||||||
target={"#collectionId"}
|
|
||||||
calloutProps={{ gapSpace: 16 }}
|
|
||||||
primaryButtonProps={{ text: "Next", onClick: () => this.setState({ teachingBubbleStep: 4 }) }}
|
|
||||||
secondaryButtonProps={{ text: "Previous", onClick: () => this.setState({ teachingBubbleStep: 2 }) }}
|
|
||||||
onDismiss={() => this.setState({ teachingBubbleStep: 0 })}
|
|
||||||
footerContent="Step 3 of 4"
|
|
||||||
>
|
|
||||||
Name your container
|
|
||||||
</TeachingBubble>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{this.state.teachingBubbleStep === 4 && (
|
|
||||||
<TeachingBubble
|
|
||||||
headline="Setting partition key"
|
|
||||||
target={"#addCollection-partitionKeyValue"}
|
|
||||||
calloutProps={{ gapSpace: 16 }}
|
|
||||||
primaryButtonProps={{
|
|
||||||
text: "Create container",
|
|
||||||
onClick: () => {
|
|
||||||
this.setState({ teachingBubbleStep: 5 });
|
|
||||||
this.submit();
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
secondaryButtonProps={{ text: "Previous", onClick: () => this.setState({ teachingBubbleStep: 2 }) }}
|
|
||||||
onDismiss={() => this.setState({ teachingBubbleStep: 0 })}
|
|
||||||
footerContent="Step 4 of 4"
|
|
||||||
>
|
|
||||||
Last step - you will need to define a partition key for your collection. /address was chosen for this
|
|
||||||
particular example. A good partition key should have a wide range of possible value
|
|
||||||
</TeachingBubble>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
<Stack hidden={userContext.apiType === "Tables"}>
|
<Stack hidden={userContext.apiType === "Tables"}>
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
@@ -345,12 +247,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated}
|
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated}
|
||||||
isDatabase={true}
|
isDatabase={true}
|
||||||
isSharded={this.state.isSharded}
|
isSharded={this.state.isSharded}
|
||||||
isFreeTier={this.isFreeTierAccount()}
|
|
||||||
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
|
||||||
setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) =>
|
|
||||||
this.setState({ isThroughputCapExceeded })
|
|
||||||
}
|
|
||||||
onCostAcknowledgeChange={(isAcknowledge: boolean) => (this.isCostAcknowledged = isAcknowledge)}
|
onCostAcknowledgeChange={(isAcknowledge: boolean) => (this.isCostAcknowledged = isAcknowledge)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -376,7 +274,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<span className="mandatoryStar">* </span>
|
<span className="mandatoryStar">* </span>
|
||||||
<Text className="panelTextBold" variant="small">
|
<Text className="panelTextBold" variant="small">
|
||||||
{`${getCollectionName()} id`}
|
{`${getCollectionName()} ${userContext.apiType === "Mongo" ? "name" : "id"}`}
|
||||||
</Text>
|
</Text>
|
||||||
<TooltipHost
|
<TooltipHost
|
||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||||
@@ -580,12 +478,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated}
|
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated}
|
||||||
isDatabase={false}
|
isDatabase={false}
|
||||||
isSharded={this.state.isSharded}
|
isSharded={this.state.isSharded}
|
||||||
isFreeTier={this.isFreeTierAccount()}
|
|
||||||
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)}
|
||||||
setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) =>
|
|
||||||
this.setState({ isThroughputCapExceeded })
|
|
||||||
}
|
|
||||||
onCostAcknowledgeChange={(isAcknowledged: boolean) => {
|
onCostAcknowledgeChange={(isAcknowledged: boolean) => {
|
||||||
this.isCostAcknowledged = isAcknowledged;
|
this.isCostAcknowledged = isAcknowledged;
|
||||||
}}
|
}}
|
||||||
@@ -765,7 +659,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
|
|
||||||
{userContext.apiType === "SQL" && (
|
{userContext.apiType === "SQL" && (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="My partition key is larger than 101 bytes"
|
label="My partition key is larger than 100 bytes"
|
||||||
checked={this.state.useHashV2}
|
checked={this.state.useHashV2}
|
||||||
styles={{
|
styles={{
|
||||||
text: { fontSize: 12 },
|
text: { fontSize: 12 },
|
||||||
@@ -782,37 +676,9 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PanelFooterComponent buttonLabel="OK" isButtonDisabled={this.state.isThroughputCapExceeded} />
|
<PanelFooterComponent buttonLabel="OK" />
|
||||||
|
|
||||||
{this.state.isExecuting && (
|
{this.state.isExecuting && <PanelLoadingScreen />}
|
||||||
<div>
|
|
||||||
<PanelLoadingScreen />
|
|
||||||
{this.state.teachingBubbleStep === 5 && (
|
|
||||||
<TeachingBubble
|
|
||||||
headline="Creating sample container"
|
|
||||||
target={"#loadingScreen"}
|
|
||||||
onDismiss={() => this.setState({ teachingBubbleStep: 0 })}
|
|
||||||
styles={{ footer: { width: "100%" } }}
|
|
||||||
>
|
|
||||||
A sample container is now being created and we are adding sample data for you. It should take about 1
|
|
||||||
minute.
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
Once the sample container is created, review your sample dataset and follow next steps
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<ProgressIndicator
|
|
||||||
styles={{
|
|
||||||
itemName: { color: "white" },
|
|
||||||
progressTrack: { backgroundColor: "#A6A6A6" },
|
|
||||||
progressBar: { background: "white" },
|
|
||||||
}}
|
|
||||||
label="Adding sample data set"
|
|
||||||
/>
|
|
||||||
</TeachingBubble>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -956,9 +822,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
if (userContext.features.partitionKeyDefault2) {
|
if (userContext.features.partitionKeyDefault2) {
|
||||||
return userContext.apiType === "SQL" ? "/pk" : "pk";
|
return userContext.apiType === "SQL" ? "/pk" : "pk";
|
||||||
}
|
}
|
||||||
if (this.props.isQuickstart) {
|
|
||||||
return userContext.apiType === "SQL" ? "/address" : "address";
|
|
||||||
}
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1016,6 +879,10 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isServerlessAccount()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
switch (userContext.apiType) {
|
switch (userContext.apiType) {
|
||||||
case "SQL":
|
case "SQL":
|
||||||
case "Mongo":
|
case "Mongo":
|
||||||
@@ -1026,11 +893,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private isSynapseLinkEnabled(): boolean {
|
private isSynapseLinkEnabled(): boolean {
|
||||||
if (!userContext.databaseAccount) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { properties } = userContext.databaseAccount;
|
const { properties } = userContext.databaseAccount;
|
||||||
|
|
||||||
if (!properties) {
|
if (!properties) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1126,25 +990,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
document.getElementById("collapsibleSectionContent")?.scrollIntoView();
|
document.getElementById("collapsibleSectionContent")?.scrollIntoView();
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSampleDBName(): string {
|
private async submit(event: React.FormEvent<HTMLFormElement>): Promise<void> {
|
||||||
const existingSampleDBs = useDatabases
|
event.preventDefault();
|
||||||
.getState()
|
|
||||||
.databases?.filter((database) => database.id().startsWith("SampleDB"));
|
|
||||||
const existingSampleDBNames = existingSampleDBs?.map((database) => database.id());
|
|
||||||
if (!existingSampleDBNames || existingSampleDBNames.length === 0) {
|
|
||||||
return "SampleDB";
|
|
||||||
}
|
|
||||||
|
|
||||||
let i = 1;
|
|
||||||
while (existingSampleDBNames.indexOf(`SampleDB${i}`) !== -1) {
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `SampleDB${i}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async submit(event?: React.FormEvent<HTMLFormElement>): Promise<void> {
|
|
||||||
event?.preventDefault();
|
|
||||||
|
|
||||||
if (!this.validateInputs()) {
|
if (!this.validateInputs()) {
|
||||||
return;
|
return;
|
||||||
@@ -1152,7 +999,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
|
|
||||||
const collectionId: string = this.state.collectionId.trim();
|
const collectionId: string = this.state.collectionId.trim();
|
||||||
let databaseId = this.state.createNewDatabase ? this.state.newDatabaseId.trim() : this.state.selectedDatabaseId;
|
let databaseId = this.state.createNewDatabase ? this.state.newDatabaseId.trim() : this.state.selectedDatabaseId;
|
||||||
let partitionKeyString = this.state.isSharded ? this.state.partitionKey.trim() : undefined;
|
let partitionKeyString = this.state.partitionKey.trim();
|
||||||
|
|
||||||
if (userContext.apiType === "Tables") {
|
if (userContext.apiType === "Tables") {
|
||||||
// Table require fixed Database: TablesDB, and fixed Partition Key: /'$pk'
|
// Table require fixed Database: TablesDB, and fixed Partition Key: /'$pk'
|
||||||
@@ -1193,7 +1040,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
subscriptionQuotaId: userContext.quotaId,
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||||
useIndexingForSharedThroughput: this.state.enableIndexing,
|
useIndexingForSharedThroughput: this.state.enableIndexing,
|
||||||
isQuickstart: !!this.props.isQuickstart,
|
|
||||||
};
|
};
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.CreateCollection, telemetryData);
|
const startKey: number = TelemetryProcessor.traceStart(Action.CreateCollection, telemetryData);
|
||||||
|
|
||||||
@@ -1238,27 +1084,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await createCollection(createCollectionParams);
|
await createCollection(createCollectionParams);
|
||||||
await this.props.explorer.refreshAllDatabases();
|
|
||||||
if (this.props.isQuickstart) {
|
|
||||||
const database = useDatabases.getState().findDatabaseWithId(databaseId);
|
|
||||||
if (database) {
|
|
||||||
database.isSampleDB = true;
|
|
||||||
// populate sample container with sample data
|
|
||||||
await database.loadCollections();
|
|
||||||
const collection = database.findCollectionWithId(collectionId);
|
|
||||||
collection.isSampleCollection = true;
|
|
||||||
useTeachingBubble.getState().setSampleCollection(collection);
|
|
||||||
const sampleGenerator = await ContainerSampleGenerator.createSampleGeneratorAsync(this.props.explorer);
|
|
||||||
await sampleGenerator.populateContainerAsync(collection);
|
|
||||||
// auto-expand sample database + container and show teaching bubble
|
|
||||||
await database.expandDatabase();
|
|
||||||
collection.expandCollection();
|
|
||||||
useDatabases.getState().updateDatabase(database);
|
|
||||||
useTeachingBubble.getState().setIsSampleDBExpanded(true);
|
|
||||||
TelemetryProcessor.traceOpen(Action.LaunchUITour);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.setState({ isExecuting: false });
|
this.setState({ isExecuting: false });
|
||||||
|
this.props.explorer.refreshAllDatabases();
|
||||||
TelemetryProcessor.traceSuccess(Action.CreateCollection, telemetryData, startKey);
|
TelemetryProcessor.traceSuccess(Action.CreateCollection, telemetryData, startKey);
|
||||||
useSidePanel.getState().closeSidePanel();
|
useSidePanel.getState().closeSidePanel();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
);
|
);
|
||||||
const [formErrors, setFormErrors] = useState<string>("");
|
const [formErrors, setFormErrors] = useState<string>("");
|
||||||
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||||
const [isThroughputCapExceeded, setIsThroughputCapExceeded] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
|
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
|
||||||
|
|
||||||
@@ -80,9 +79,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||||
};
|
};
|
||||||
TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage);
|
TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage);
|
||||||
if (buttonElement) {
|
buttonElement.focus();
|
||||||
buttonElement.focus();
|
|
||||||
}
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
@@ -147,7 +144,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
if (isAutoscaleSelected) {
|
if (isAutoscaleSelected) {
|
||||||
if (!AutoPilotUtils.isValidAutoPilotThroughput(throughput)) {
|
if (!AutoPilotUtils.isValidAutoPilotThroughput(throughput)) {
|
||||||
setFormErrors(
|
setFormErrors(
|
||||||
`Please enter a value greater than ${AutoPilotUtils.autoPilotThroughput1K} for autopilot throughput`
|
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -172,7 +169,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
formError: formErrors,
|
formError: formErrors,
|
||||||
isExecuting,
|
isExecuting,
|
||||||
submitButtonText: "OK",
|
submitButtonText: "OK",
|
||||||
isSubmitButtonDisabled: isThroughputCapExceeded,
|
|
||||||
onSubmit,
|
onSubmit,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -241,10 +237,8 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
|
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
|
||||||
isDatabase={true}
|
isDatabase={true}
|
||||||
isSharded={databaseCreateNewShared}
|
isSharded={databaseCreateNewShared}
|
||||||
isFreeTier={isFreeTierAccount}
|
|
||||||
setThroughputValue={(newThroughput: number) => (throughput = newThroughput)}
|
setThroughputValue={(newThroughput: number) => (throughput = newThroughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => (isAutoscaleSelected = isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (isAutoscaleSelected = isAutoscale)}
|
||||||
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}
|
|
||||||
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
|||||||
<RightPaneForm
|
<RightPaneForm
|
||||||
formError=""
|
formError=""
|
||||||
isExecuting={false}
|
isExecuting={false}
|
||||||
isSubmitButtonDisabled={false}
|
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
submitButtonText="OK"
|
submitButtonText="OK"
|
||||||
>
|
>
|
||||||
@@ -93,7 +92,6 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
|||||||
isSharded={true}
|
isSharded={true}
|
||||||
onCostAcknowledgeChange={[Function]}
|
onCostAcknowledgeChange={[Function]}
|
||||||
setIsAutoscale={[Function]}
|
setIsAutoscale={[Function]}
|
||||||
setIsThroughputCapExceeded={[Function]}
|
|
||||||
setThroughputValue={[Function]}
|
setThroughputValue={[Function]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { Checkbox, Dropdown, IDropdownOption, Link, Stack, Text, TextField } from "@fluentui/react";
|
import { Checkbox, Dropdown, IDropdownOption, Link, Stack, Text, TextField } from "@fluentui/react";
|
||||||
import * as Constants from "Common/Constants";
|
|
||||||
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
|
||||||
import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
|
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
|
||||||
import React, { FunctionComponent, useState } from "react";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
import * as SharedConstants from "Shared/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||||
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
|
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
||||||
import { userContext } from "UserContext";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import { isServerlessAccount } from "Utils/CapabilityUtils";
|
import * as SharedConstants from "../../../Shared/Constants";
|
||||||
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { userContext } from "../../../UserContext";
|
||||||
|
import { isServerlessAccount } from "../../../Utils/CapabilityUtils";
|
||||||
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
|
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
|
import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
|
||||||
@@ -43,7 +43,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
const [dedicateTableThroughput, setDedicateTableThroughput] = useState<boolean>(false);
|
const [dedicateTableThroughput, setDedicateTableThroughput] = useState<boolean>(false);
|
||||||
const [isExecuting, setIsExecuting] = useState<boolean>();
|
const [isExecuting, setIsExecuting] = useState<boolean>();
|
||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
const [isThroughputCapExceeded, setIsThroughputCapExceeded] = useState<boolean>(false);
|
|
||||||
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
|
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
|
||||||
|
|
||||||
const addCollectionPaneOpenMessage = {
|
const addCollectionPaneOpenMessage = {
|
||||||
@@ -150,7 +149,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
formError,
|
formError,
|
||||||
isExecuting,
|
isExecuting,
|
||||||
submitButtonText: "OK",
|
submitButtonText: "OK",
|
||||||
isSubmitButtonDisabled: isThroughputCapExceeded,
|
|
||||||
onSubmit,
|
onSubmit,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -262,10 +260,8 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
}
|
}
|
||||||
isDatabase
|
isDatabase
|
||||||
isSharded
|
isSharded
|
||||||
isFreeTier={isFreeTierAccount}
|
|
||||||
setThroughputValue={(throughput: number) => (newKeySpaceThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (newKeySpaceThroughput = throughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => (isNewKeySpaceAutoscale = isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (isNewKeySpaceAutoscale = isAutoscale)}
|
||||||
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}
|
|
||||||
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -335,11 +331,9 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
|
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
|
||||||
isDatabase={false}
|
isDatabase={false}
|
||||||
isSharded
|
isSharded={false}
|
||||||
isFreeTier={isFreeTierAccount}
|
|
||||||
setThroughputValue={(throughput: number) => (tableThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (tableThroughput = throughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => (isTableAutoscale = isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (isTableAutoscale = isAutoscale)}
|
||||||
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}
|
|
||||||
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils
|
|||||||
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient";
|
import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient";
|
||||||
|
import { userContext } from "../../../UserContext";
|
||||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { NotebookContentItem, NotebookContentItemType } from "../../Notebook/NotebookContentItem";
|
import { NotebookContentItem, NotebookContentItemType } from "../../Notebook/NotebookContentItem";
|
||||||
import { useNotebook } from "../../Notebook/useNotebook";
|
import { useNotebook } from "../../Notebook/useNotebook";
|
||||||
|
import { ResourceTreeAdapter } from "../../Tree/ResourceTreeAdapter";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent";
|
import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent";
|
||||||
|
|
||||||
@@ -74,7 +76,7 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
|||||||
selectedLocation.owner,
|
selectedLocation.owner,
|
||||||
selectedLocation.repo
|
selectedLocation.repo
|
||||||
)} - ${selectedLocation.branch}`;
|
)} - ${selectedLocation.branch}`;
|
||||||
} else if (selectedLocation.type === "MyNotebooks" && useNotebook.getState().isPhoenixNotebooks) {
|
} else if (selectedLocation.type === "MyNotebooks" && userContext.features.phoenix) {
|
||||||
destination = useNotebook.getState().notebookFolderName;
|
destination = useNotebook.getState().notebookFolderName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,14 +105,11 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
|||||||
switch (location.type) {
|
switch (location.type) {
|
||||||
case "MyNotebooks":
|
case "MyNotebooks":
|
||||||
parent = {
|
parent = {
|
||||||
name: useNotebook.getState().notebookFolderName,
|
name: ResourceTreeAdapter.MyNotebooksTitle,
|
||||||
path: useNotebook.getState().notebookBasePath,
|
path: useNotebook.getState().notebookBasePath,
|
||||||
type: NotebookContentItemType.Directory,
|
type: NotebookContentItemType.Directory,
|
||||||
};
|
};
|
||||||
isGithubTree = false;
|
isGithubTree = false;
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
|
||||||
await container.allocateContainer();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "GitHub":
|
case "GitHub":
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { Text, TextField } from "@fluentui/react";
|
import { Text, TextField } from "@fluentui/react";
|
||||||
import { Areas } from "Common/Constants";
|
|
||||||
import { deleteCollection } from "Common/dataAccess/deleteCollection";
|
|
||||||
import DeleteFeedback from "Common/DeleteFeedback";
|
|
||||||
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
|
||||||
import { Collection } from "Contracts/ViewModels";
|
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
|
||||||
import { useTabs } from "hooks/useTabs";
|
|
||||||
import React, { FunctionComponent, useState } from "react";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
import { DefaultExperienceUtility } from "Shared/DefaultExperienceUtility";
|
import { Areas } from "../../../Common/Constants";
|
||||||
import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants";
|
import { deleteCollection } from "../../../Common/dataAccess/deleteCollection";
|
||||||
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
|
import DeleteFeedback from "../../../Common/DeleteFeedback";
|
||||||
import { userContext } from "UserContext";
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||||
import { getCollectionName } from "Utils/APITypeUtils";
|
import { Collection } from "../../../Contracts/ViewModels";
|
||||||
import * as NotificationConsoleUtils from "Utils/NotificationConsoleUtils";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
|
import { DefaultExperienceUtility } from "../../../Shared/DefaultExperienceUtility";
|
||||||
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { userContext } from "../../../UserContext";
|
||||||
|
import { getCollectionName } from "../../../Utils/APITypeUtils";
|
||||||
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
import { useDatabases } from "../../useDatabases";
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { useSelectedNode } from "../../useSelectedNode";
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
@@ -38,7 +38,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
const onSubmit = async (): Promise<void> => {
|
const onSubmit = async (): Promise<void> => {
|
||||||
const collection = useSelectedNode.getState().findSelectedCollection();
|
const collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
if (!collection || inputCollectionName !== collection.id()) {
|
if (!collection || inputCollectionName !== collection.id()) {
|
||||||
const errorMessage = "Input " + collectionName + " id does not match the selected " + collectionName;
|
const errorMessage = "Input " + collectionName + " name does not match the selected " + collectionName;
|
||||||
setFormError(errorMessage);
|
setFormError(errorMessage);
|
||||||
NotificationConsoleUtils.logConsoleError(
|
NotificationConsoleUtils.logConsoleError(
|
||||||
`Error while deleting ${collectionName} ${collection.id()}: ${errorMessage}`
|
`Error while deleting ${collectionName} ${collection.id()}: ${errorMessage}`
|
||||||
|
|||||||
@@ -369,21 +369,18 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
</div>
|
</div>
|
||||||
<PanelFooterComponent
|
<PanelFooterComponent
|
||||||
buttonLabel="OK"
|
buttonLabel="OK"
|
||||||
isButtonDisabled={false}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="panelFooter"
|
className="panelFooter"
|
||||||
>
|
>
|
||||||
<CustomizedPrimaryButton
|
<CustomizedPrimaryButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="OK"
|
text="OK"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="OK"
|
text="OK"
|
||||||
theme={
|
theme={
|
||||||
@@ -663,7 +660,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
>
|
>
|
||||||
<CustomizedDefaultButton
|
<CustomizedDefaultButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
@@ -945,7 +941,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
>
|
>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
@@ -1228,7 +1223,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
baseClassName="ms-Button"
|
baseClassName="ms-Button"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { Text, TextField } from "@fluentui/react";
|
import { Text, TextField } from "@fluentui/react";
|
||||||
import { useBoolean } from "@fluentui/react-hooks";
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
import { Areas } from "Common/Constants";
|
|
||||||
import { deleteDatabase } from "Common/dataAccess/deleteDatabase";
|
|
||||||
import DeleteFeedback from "Common/DeleteFeedback";
|
|
||||||
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
|
||||||
import { Collection, Database } from "Contracts/ViewModels";
|
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
|
||||||
import { useTabs } from "hooks/useTabs";
|
|
||||||
import React, { FunctionComponent, useState } from "react";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
import { DefaultExperienceUtility } from "Shared/DefaultExperienceUtility";
|
import { Areas } from "../../Common/Constants";
|
||||||
import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants";
|
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
|
||||||
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
|
import DeleteFeedback from "../../Common/DeleteFeedback";
|
||||||
import { userContext } from "UserContext";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
import { Collection, Database } from "../../Contracts/ViewModels";
|
||||||
|
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||||
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
|
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
||||||
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { useDatabases } from "../useDatabases";
|
import { useDatabases } from "../useDatabases";
|
||||||
import { useSelectedNode } from "../useSelectedNode";
|
import { useSelectedNode } from "../useSelectedNode";
|
||||||
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
|
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { IDropdownOption, IImageProps, Image, Stack, Text } from "@fluentui/react";
|
import { IDropdownOption, IImageProps, Image, Stack, Text } from "@fluentui/react";
|
||||||
import { useBoolean } from "@fluentui/react-hooks";
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
import React, { FunctionComponent, useRef, useState } from "react";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||||
@@ -25,16 +25,19 @@ interface UnwrappedExecuteSprocParam {
|
|||||||
export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPaneProps> = ({
|
export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPaneProps> = ({
|
||||||
storedProcedure,
|
storedProcedure,
|
||||||
}: ExecuteSprocParamsPaneProps): JSX.Element => {
|
}: ExecuteSprocParamsPaneProps): JSX.Element => {
|
||||||
const paramKeyValuesRef = useRef<UnwrappedExecuteSprocParam[]>([{ key: "string", text: "" }]);
|
|
||||||
const partitionValueRef = useRef<string>();
|
|
||||||
const partitionKeyRef = useRef<string>("string");
|
|
||||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
const [numberOfParams, setNumberOfParams] = useState<number>(1);
|
|
||||||
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
||||||
|
const [paramKeyValues, setParamKeyValues] = useState<UnwrappedExecuteSprocParam[]>([{ key: "string", text: "" }]);
|
||||||
|
const [partitionValue, setPartitionValue] = useState<string>(); // Defaulting to undefined here is important. It is not the same partition key as ""
|
||||||
|
const [selectedKey, setSelectedKey] = React.useState<IDropdownOption>({ key: "string", text: "" });
|
||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
|
|
||||||
|
const onPartitionKeyChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
|
||||||
|
setSelectedKey(item);
|
||||||
|
};
|
||||||
|
|
||||||
const validateUnwrappedParams = (): boolean => {
|
const validateUnwrappedParams = (): boolean => {
|
||||||
const unwrappedParams: UnwrappedExecuteSprocParam[] = paramKeyValuesRef.current;
|
const unwrappedParams: UnwrappedExecuteSprocParam[] = paramKeyValues;
|
||||||
for (let i = 0; i < unwrappedParams.length; i++) {
|
for (let i = 0; i < unwrappedParams.length; i++) {
|
||||||
const { key: paramType, text: paramValue } = unwrappedParams[i];
|
const { key: paramType, text: paramValue } = unwrappedParams[i];
|
||||||
if (paramType === "custom" && (paramValue === "" || paramValue === undefined)) {
|
if (paramType === "custom" && (paramValue === "" || paramValue === undefined)) {
|
||||||
@@ -50,9 +53,8 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
|||||||
};
|
};
|
||||||
|
|
||||||
const submit = (): void => {
|
const submit = (): void => {
|
||||||
const wrappedSprocParams: UnwrappedExecuteSprocParam[] = paramKeyValuesRef.current;
|
const wrappedSprocParams: UnwrappedExecuteSprocParam[] = paramKeyValues;
|
||||||
const partitionValue: string = partitionValueRef.current;
|
const { key: partitionKey } = selectedKey;
|
||||||
const partitionKey: string = partitionKeyRef.current;
|
|
||||||
if (partitionKey === "custom" && (partitionValue === "" || partitionValue === undefined)) {
|
if (partitionKey === "custom" && (partitionValue === "" || partitionValue === undefined)) {
|
||||||
setInvalidParamError(partitionValue);
|
setInvalidParamError(partitionValue);
|
||||||
return;
|
return;
|
||||||
@@ -76,21 +78,37 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
|||||||
};
|
};
|
||||||
|
|
||||||
const deleteParamAtIndex = (indexToRemove: number): void => {
|
const deleteParamAtIndex = (indexToRemove: number): void => {
|
||||||
paramKeyValuesRef.current.splice(indexToRemove, 1);
|
const cloneParamKeyValue = [...paramKeyValues];
|
||||||
setNumberOfParams(numberOfParams - 1);
|
cloneParamKeyValue.splice(indexToRemove, 1);
|
||||||
|
setParamKeyValues(cloneParamKeyValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addNewParamAtIndex = (indexToAdd: number): void => {
|
const addNewParamAtIndex = (indexToAdd: number): void => {
|
||||||
paramKeyValuesRef.current.splice(indexToAdd, 0, { key: "string", text: "" });
|
const cloneParamKeyValue = [...paramKeyValues];
|
||||||
setNumberOfParams(numberOfParams + 1);
|
cloneParamKeyValue.splice(indexToAdd, 0, { key: "string", text: "" });
|
||||||
|
setParamKeyValues(cloneParamKeyValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const paramValueChange = (value: string, indexOfInput: number): void => {
|
||||||
|
const cloneParamKeyValue = [...paramKeyValues];
|
||||||
|
cloneParamKeyValue[indexOfInput].text = value;
|
||||||
|
setParamKeyValues(cloneParamKeyValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const paramKeyChange = (
|
||||||
|
_event: React.FormEvent<HTMLDivElement>,
|
||||||
|
selectedParam: IDropdownOption,
|
||||||
|
indexOfParam: number
|
||||||
|
): void => {
|
||||||
|
const cloneParamKeyValue = [...paramKeyValues];
|
||||||
|
cloneParamKeyValue[indexOfParam].key = selectedParam.key.toString();
|
||||||
|
setParamKeyValues(cloneParamKeyValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addNewParamAtLastIndex = (): void => {
|
const addNewParamAtLastIndex = (): void => {
|
||||||
paramKeyValuesRef.current.push({
|
const cloneParamKeyValue = [...paramKeyValues];
|
||||||
key: "string",
|
cloneParamKeyValue.splice(cloneParamKeyValue.length, 0, { key: "string", text: "" });
|
||||||
text: "",
|
setParamKeyValues(cloneParamKeyValue);
|
||||||
});
|
|
||||||
setNumberOfParams(numberOfParams + 1);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const props: RightPaneFormProps = {
|
const props: RightPaneFormProps = {
|
||||||
@@ -100,52 +118,46 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
|||||||
onSubmit: () => submit(),
|
onSubmit: () => submit(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const getInputParameterComponent = (): JSX.Element[] => {
|
|
||||||
const inputParameters: JSX.Element[] = [];
|
|
||||||
for (let i = 0; i < numberOfParams; i++) {
|
|
||||||
const paramKeyValue = paramKeyValuesRef.current[i];
|
|
||||||
inputParameters.push(
|
|
||||||
<InputParameter
|
|
||||||
key={paramKeyValue.text + i}
|
|
||||||
dropdownLabel={i === 0 ? "Key" : ""}
|
|
||||||
inputParameterTitle={i === 0 ? "Enter input parameters (if any)" : ""}
|
|
||||||
inputLabel={i === 0 ? "Param" : ""}
|
|
||||||
isAddRemoveVisible={true}
|
|
||||||
onDeleteParamKeyPress={() => deleteParamAtIndex(i)}
|
|
||||||
onAddNewParamKeyPress={() => addNewParamAtIndex(i + 1)}
|
|
||||||
onParamValueChange={(_event, newInput?: string) => (paramKeyValuesRef.current[i].text = newInput)}
|
|
||||||
onParamKeyChange={(_event, selectedParam: IDropdownOption) =>
|
|
||||||
(paramKeyValuesRef.current[i].key = selectedParam.key.toString())
|
|
||||||
}
|
|
||||||
paramValue={paramKeyValue.text}
|
|
||||||
selectedKey={paramKeyValue.key}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return inputParameters;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<RightPaneForm {...props}>
|
||||||
<div className="panelMainContent">
|
<div className="panelFormWrapper">
|
||||||
<InputParameter
|
<div className="panelMainContent">
|
||||||
dropdownLabel="Key"
|
<InputParameter
|
||||||
inputParameterTitle="Partition key value"
|
dropdownLabel="Key"
|
||||||
inputLabel="Value"
|
inputParameterTitle="Partition key value"
|
||||||
isAddRemoveVisible={false}
|
inputLabel="Value"
|
||||||
onParamValueChange={(_event, newInput?: string) => (partitionValueRef.current = newInput)}
|
isAddRemoveVisible={false}
|
||||||
onParamKeyChange={(_event: React.FormEvent<HTMLDivElement>, item: IDropdownOption) =>
|
onParamValueChange={(_event, newInput?: string) => {
|
||||||
(partitionKeyRef.current = item.key.toString())
|
setPartitionValue(newInput);
|
||||||
}
|
}}
|
||||||
paramValue={partitionValueRef.current}
|
onParamKeyChange={onPartitionKeyChange}
|
||||||
selectedKey={partitionKeyRef.current}
|
paramValue={partitionValue}
|
||||||
/>
|
selectedKey={selectedKey.key}
|
||||||
{getInputParameterComponent()}
|
/>
|
||||||
<Stack horizontal onClick={() => addNewParamAtLastIndex()} tabIndex={0}>
|
{paramKeyValues.map((paramKeyValue, index) => (
|
||||||
<Image {...imageProps} src={AddPropertyIcon} alt="Add param" />
|
<InputParameter
|
||||||
<Text className="addNewParamStyle">Add New Param</Text>
|
key={paramKeyValue && paramKeyValue.text + index}
|
||||||
</Stack>
|
dropdownLabel={!index && "Key"}
|
||||||
|
inputParameterTitle={!index && "Enter input parameters (if any)"}
|
||||||
|
inputLabel={!index && "Param"}
|
||||||
|
isAddRemoveVisible={true}
|
||||||
|
onDeleteParamKeyPress={() => deleteParamAtIndex(index)}
|
||||||
|
onAddNewParamKeyPress={() => addNewParamAtIndex(index + 1)}
|
||||||
|
onParamValueChange={(event, newInput?: string) => {
|
||||||
|
paramValueChange(newInput, index);
|
||||||
|
}}
|
||||||
|
onParamKeyChange={(event: React.FormEvent<HTMLDivElement>, selectedParam: IDropdownOption) => {
|
||||||
|
paramKeyChange(event, selectedParam, index);
|
||||||
|
}}
|
||||||
|
paramValue={paramKeyValue && paramKeyValue.text}
|
||||||
|
selectedKey={paramKeyValue && paramKeyValue.key}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<Stack horizontal onClick={addNewParamAtLastIndex} tabIndex={0}>
|
||||||
|
<Image {...imageProps} src={AddPropertyIcon} alt="Add param" />
|
||||||
|
<Text className="addNewParamStyle">Add New Param</Text>
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</RightPaneForm>
|
</RightPaneForm>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export const InputParameter: FunctionComponent<InputParameterProps> = ({
|
|||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
label={dropdownLabel && dropdownLabel}
|
label={dropdownLabel && dropdownLabel}
|
||||||
defaultSelectedKey={selectedKey}
|
selectedKey={selectedKey}
|
||||||
onChange={onParamKeyChange}
|
onChange={onParamKeyChange}
|
||||||
options={options}
|
options={options}
|
||||||
styles={dropdownStyles}
|
styles={dropdownStyles}
|
||||||
@@ -64,9 +64,8 @@ export const InputParameter: FunctionComponent<InputParameterProps> = ({
|
|||||||
<TextField
|
<TextField
|
||||||
label={inputLabel && inputLabel}
|
label={inputLabel && inputLabel}
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
defaultValue={paramValue}
|
value={paramValue}
|
||||||
onChange={onParamValueChange}
|
onChange={onParamValueChange}
|
||||||
tabIndex={0}
|
|
||||||
/>
|
/>
|
||||||
{isAddRemoveVisible && (
|
{isAddRemoveVisible && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ interface IGitHubReposPanelState {
|
|||||||
}
|
}
|
||||||
export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IGitHubReposPanelState> {
|
export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IGitHubReposPanelState> {
|
||||||
private static readonly PageSize = 30;
|
private static readonly PageSize = 30;
|
||||||
private static readonly MasterBranchName = "master";
|
|
||||||
private static readonly MainBranchName = "main";
|
|
||||||
|
|
||||||
private isAddedRepo = false;
|
private isAddedRepo = false;
|
||||||
private gitHubClient: GitHubClient;
|
private gitHubClient: GitHubClient;
|
||||||
private junoClient: JunoClient;
|
private junoClient: JunoClient;
|
||||||
@@ -119,8 +116,6 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
|||||||
if (response.status !== HttpStatusCodes.OK) {
|
if (response.status !== HttpStatusCodes.OK) {
|
||||||
throw new Error(`Received HTTP ${response.status} when saving pinned repos`);
|
throw new Error(`Received HTTP ${response.status} when saving pinned repos`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.explorer.notebookManager?.refreshPinnedRepos();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, "GitHubReposPane/submit", "Failed to save pinned repos");
|
handleError(error, "GitHubReposPane/submit", "Failed to save pinned repos");
|
||||||
}
|
}
|
||||||
@@ -212,14 +207,6 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
|||||||
if (response.data) {
|
if (response.data) {
|
||||||
branchesProps.branches = branchesProps.branches.concat(response.data);
|
branchesProps.branches = branchesProps.branches.concat(response.data);
|
||||||
branchesProps.lastPageInfo = response.pageInfo;
|
branchesProps.lastPageInfo = response.pageInfo;
|
||||||
branchesProps.defaultBranchName = branchesProps.branches[0].name;
|
|
||||||
const defaultbranchName = branchesProps.branches.find(
|
|
||||||
(branch) =>
|
|
||||||
branch.name === GitHubReposPanel.MasterBranchName || branch.name === GitHubReposPanel.MainBranchName
|
|
||||||
)?.name;
|
|
||||||
if (defaultbranchName) {
|
|
||||||
branchesProps.defaultBranchName = defaultbranchName;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, "GitHubReposPane/loadMoreBranches", "Failed to fetch branches");
|
handleError(error, "GitHubReposPane/loadMoreBranches", "Failed to fetch branches");
|
||||||
@@ -311,17 +298,6 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
|||||||
const existingRepo = this.pinnedReposProps.repos.find((repo) => repo.key === item.key);
|
const existingRepo = this.pinnedReposProps.repos.find((repo) => repo.key === item.key);
|
||||||
if (existingRepo) {
|
if (existingRepo) {
|
||||||
existingRepo.branches = item.branches;
|
existingRepo.branches = item.branches;
|
||||||
this.setState({
|
|
||||||
gitHubReposState: {
|
|
||||||
...this.state.gitHubReposState,
|
|
||||||
reposListProps: {
|
|
||||||
...this.state.gitHubReposState.reposListProps,
|
|
||||||
pinnedReposProps: {
|
|
||||||
repos: this.pinnedReposProps.repos,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
this.pinnedReposProps.repos = [...this.pinnedReposProps.repos, item];
|
this.pinnedReposProps.repos = [...this.pinnedReposProps.repos, item];
|
||||||
}
|
}
|
||||||
@@ -398,7 +374,6 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
|||||||
lastPageInfo: undefined,
|
lastPageInfo: undefined,
|
||||||
hasMore: true,
|
hasMore: true,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
defaultBranchName: undefined,
|
|
||||||
loadMore: (): Promise<void> => this.loadMoreBranches(item.repo),
|
loadMore: (): Promise<void> => this.loadMoreBranches(item.repo),
|
||||||
};
|
};
|
||||||
this.loadMoreBranches(item.repo);
|
this.loadMoreBranches(item.repo);
|
||||||
|
|||||||
@@ -23,13 +23,7 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
|||||||
"isTabsContentExpanded": [Function],
|
"isTabsContentExpanded": [Function],
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
"onRefreshDatabasesKeyPress": [Function],
|
||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"phoenixClient": PhoenixClient {
|
"phoenixClient": PhoenixClient {},
|
||||||
"retryOptions": Object {
|
|
||||||
"maxTimeout": 5000,
|
|
||||||
"minTimeout": 5000,
|
|
||||||
"retries": 3,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
|
|||||||
@@ -3,20 +3,12 @@ import React from "react";
|
|||||||
|
|
||||||
export interface PanelFooterProps {
|
export interface PanelFooterProps {
|
||||||
buttonLabel: string;
|
buttonLabel: string;
|
||||||
isButtonDisabled?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PanelFooterComponent: React.FunctionComponent<PanelFooterProps> = ({
|
export const PanelFooterComponent: React.FunctionComponent<PanelFooterProps> = ({
|
||||||
buttonLabel,
|
buttonLabel,
|
||||||
isButtonDisabled,
|
|
||||||
}: PanelFooterProps): JSX.Element => (
|
}: PanelFooterProps): JSX.Element => (
|
||||||
<div className="panelFooter">
|
<div className="panelFooter">
|
||||||
<PrimaryButton
|
<PrimaryButton type="submit" id="sidePanelOkButton" text={buttonLabel} ariaLabel={buttonLabel} />
|
||||||
type="submit"
|
|
||||||
id="sidePanelOkButton"
|
|
||||||
text={buttonLabel}
|
|
||||||
ariaLabel={buttonLabel}
|
|
||||||
disabled={!!isButtonDisabled}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||