Compare commits
178 Commits
all-checks
...
eslint/Not
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a3e6f6aef | ||
|
|
271256bffb | ||
|
|
aff7133095 | ||
|
|
bfd4948fb9 | ||
|
|
7687ed1483 | ||
|
|
1c54459708 | ||
|
|
df3b18d585 | ||
|
|
882f0e1554 | ||
|
|
b67b76cc87 | ||
|
|
734ee1e436 | ||
|
|
ff498b51e2 | ||
|
|
ed1ffb692f | ||
|
|
f7fa3f7c09 | ||
|
|
6ebf19c0c9 | ||
|
|
f968f57543 | ||
|
|
6ca8e3c6f4 | ||
|
|
f7e7240010 | ||
|
|
acc095a482 | ||
|
|
288e6410f3 | ||
|
|
9ac3392271 | ||
|
|
9a908dde9a | ||
|
|
ddd2e63fe7 | ||
|
|
34c59b4872 | ||
|
|
7d28af4fc7 | ||
|
|
50b99ceb42 | ||
|
|
15a26d6500 | ||
|
|
a8150af269 | ||
|
|
6a9a0156a3 | ||
|
|
ead28f043f | ||
|
|
b05e5a2145 | ||
|
|
5e8aa491ba | ||
|
|
a730c08292 | ||
|
|
3dce5cd129 | ||
|
|
7c186c3ef2 | ||
|
|
d8840a0dfd | ||
|
|
f853c4ec2f | ||
|
|
9bf5f48165 | ||
|
|
0b2a204b70 | ||
|
|
c28593b752 | ||
|
|
4cbbef9574 | ||
|
|
300c952a9b | ||
|
|
38c3761260 | ||
|
|
3032f689b6 | ||
|
|
8b30af3d9e | ||
|
|
e10240bd7a | ||
|
|
ae9c27795e | ||
|
|
d7997d716e | ||
|
|
af0dc3094b | ||
|
|
665270296f | ||
|
|
2d945c8231 | ||
|
|
8866976bb4 | ||
|
|
d10f3c69f1 | ||
|
|
7e4f030547 | ||
|
|
05f46dd635 | ||
|
|
65882ea831 | ||
|
|
95c9b7ee31 | ||
|
|
39dd293fc1 | ||
|
|
8eeda41021 | ||
|
|
960cd9fc55 | ||
|
|
9ec0ac9f54 | ||
|
|
b66aeb814a | ||
|
|
410f582378 | ||
|
|
678ca51c77 | ||
|
|
2dfd90ca0f | ||
|
|
4110be10bd | ||
|
|
6e55e397b3 | ||
|
|
24d0a16123 | ||
|
|
f8ac36962b | ||
|
|
51f3f9a718 | ||
|
|
ee4404c439 | ||
|
|
b65456f754 | ||
|
|
56699ccb1b | ||
|
|
042f980b89 | ||
|
|
7e0c4b7290 | ||
|
|
a66fc06dad | ||
|
|
a8bc821dec | ||
|
|
1394aae944 | ||
|
|
fecac5625a | ||
|
|
dc21032d69 | ||
|
|
39a67dbc98 | ||
|
|
ed9cf01b50 | ||
|
|
e443d17b2e | ||
|
|
401660ae15 | ||
|
|
c5e4ee9c2b | ||
|
|
913fec4e69 | ||
|
|
6d46e48490 | ||
|
|
afacde4041 | ||
|
|
8a3929775b | ||
|
|
b115bb34ca | ||
|
|
397231dca2 | ||
|
|
0bbf9de963 | ||
|
|
103b3bf6c9 | ||
|
|
7dd8bd567f | ||
|
|
416358f540 | ||
|
|
62b483d740 | ||
|
|
c665c4bb7a | ||
|
|
887618e77e | ||
|
|
8d6ccf8356 | ||
|
|
4614ab3427 | ||
|
|
71113e403e | ||
|
|
854bd2c149 | ||
|
|
cfce78242c | ||
|
|
ee3488d3a9 | ||
|
|
e8d320e505 | ||
|
|
f8ab0a82e0 | ||
|
|
f4eef1b61b | ||
|
|
c486c1193e | ||
|
|
db34024259 | ||
|
|
98d7bb37d5 | ||
|
|
45d0b3f706 | ||
|
|
a1d5648bbc | ||
|
|
447db01647 | ||
|
|
4d2a6999d4 | ||
|
|
a7239c7579 | ||
|
|
c1d4008895 | ||
|
|
59655eed5f | ||
|
|
6b35ab03f2 | ||
|
|
738a02a0f3 | ||
|
|
b392bed1b0 | ||
|
|
f255387ccd | ||
|
|
f9bd12eaa6 | ||
|
|
39215dc4de | ||
|
|
96e6bba38b | ||
|
|
c9fa44f6f4 | ||
|
|
05f59307c2 | ||
|
|
1d449e5b52 | ||
|
|
6f68c75257 | ||
|
|
914c372f5b | ||
|
|
af71a96d54 | ||
|
|
239c7edf7b | ||
|
|
0c6324a4c1 | ||
|
|
615bfeaf48 | ||
|
|
3bc58a80e4 | ||
|
|
5da9724deb | ||
|
|
999fad3bad | ||
|
|
baa3252ba8 | ||
|
|
959d34d88d | ||
|
|
ce3c2fcfb6 | ||
|
|
0a1a2bf421 | ||
|
|
b0bbeb188a | ||
|
|
fc9f287d0a | ||
|
|
006230262c | ||
|
|
6de77a4fba | ||
|
|
c980af9a5c | ||
|
|
c632342a43 | ||
|
|
bcc9f8dd32 | ||
|
|
fc9f4c5583 | ||
|
|
8f6cac3d35 | ||
|
|
2c296ede35 | ||
|
|
16b09df5fa | ||
|
|
ee60f61cfe | ||
|
|
f296c00a1c | ||
|
|
7d0be7d355 | ||
|
|
04b3ef051a | ||
|
|
b875407d49 | ||
|
|
18ce8749ed | ||
|
|
5e2b8d7df0 | ||
|
|
da13a2b3cf | ||
|
|
69b8196cf0 | ||
|
|
5417e1e120 | ||
|
|
481ff9e7fe | ||
|
|
e41b52e265 | ||
|
|
75d01f655f | ||
|
|
50f83cde87 | ||
|
|
6d03cec139 | ||
|
|
cb1d60cc90 | ||
|
|
0201e6ff92 | ||
|
|
1bcb4246f6 | ||
|
|
e7e15c54b3 | ||
|
|
522fdc69ab | ||
|
|
bfdeae56d9 | ||
|
|
c42a10faa5 | ||
|
|
0d79f01304 | ||
|
|
eae5b2219e | ||
|
|
2fda881770 | ||
|
|
35f8fa8324 | ||
|
|
0e413430dc | ||
|
|
afd7f43eb8 |
15
.env.example
@@ -1,16 +1 @@
|
|||||||
PORTAL_RUNNER_USERNAME=
|
|
||||||
PORTAL_RUNNER_PASSWORD=
|
|
||||||
PORTAL_RUNNER_SUBSCRIPTION=
|
|
||||||
PORTAL_RUNNER_RESOURCE_GROUP=
|
|
||||||
PORTAL_RUNNER_DATABASE_ACCOUNT=
|
|
||||||
PORTAL_RUNNER_DATABASE_ACCOUNT_KEY=
|
|
||||||
PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT=
|
|
||||||
PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT_KEY=
|
|
||||||
PORTAL_RUNNER_CONNECTION_STRING=
|
|
||||||
NOTEBOOKS_TEST_RUNNER_TENANT_ID=
|
|
||||||
NOTEBOOKS_TEST_RUNNER_CLIENT_ID=
|
|
||||||
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET=
|
|
||||||
CASSANDRA_CONNECTION_STRING=
|
|
||||||
MONGO_CONNECTION_STRING=
|
|
||||||
TABLES_CONNECTION_STRING=
|
|
||||||
DATA_EXPLORER_ENDPOINT=https://localhost:1234/hostedExplorer.html
|
DATA_EXPLORER_ENDPOINT=https://localhost:1234/hostedExplorer.html
|
||||||
178
.eslintignore
@@ -21,16 +21,8 @@ src/Common/MongoUtility.ts
|
|||||||
src/Common/NotificationsClientBase.ts
|
src/Common/NotificationsClientBase.ts
|
||||||
src/Common/QueriesClient.ts
|
src/Common/QueriesClient.ts
|
||||||
src/Common/Splitter.ts
|
src/Common/Splitter.ts
|
||||||
src/Config.ts
|
|
||||||
src/Contracts/ActionContracts.ts
|
|
||||||
src/Contracts/DataModels.ts
|
|
||||||
src/Contracts/Diagnostics.ts
|
|
||||||
src/Contracts/ExplorerContracts.ts
|
|
||||||
src/Contracts/Versions.ts
|
|
||||||
src/Contracts/ViewModels.ts
|
|
||||||
src/Controls/Heatmap/Heatmap.test.ts
|
src/Controls/Heatmap/Heatmap.test.ts
|
||||||
src/Controls/Heatmap/Heatmap.ts
|
src/Controls/Heatmap/Heatmap.ts
|
||||||
src/Controls/Heatmap/HeatmapDatatypes.ts
|
|
||||||
src/Definitions/datatables.d.ts
|
src/Definitions/datatables.d.ts
|
||||||
src/Definitions/gif.d.ts
|
src/Definitions/gif.d.ts
|
||||||
src/Definitions/globals.d.ts
|
src/Definitions/globals.d.ts
|
||||||
@@ -44,35 +36,14 @@ src/Definitions/png.d.ts
|
|||||||
src/Definitions/svg.d.ts
|
src/Definitions/svg.d.ts
|
||||||
src/Explorer/ComponentRegisterer.test.ts
|
src/Explorer/ComponentRegisterer.test.ts
|
||||||
src/Explorer/ComponentRegisterer.ts
|
src/Explorer/ComponentRegisterer.ts
|
||||||
src/Explorer/ContextMenuButtonFactory.ts
|
|
||||||
src/Explorer/Controls/CollapsiblePanel/CollapsiblePanelComponent.ts
|
|
||||||
src/Explorer/Controls/DiffEditor/DiffEditorComponent.ts
|
src/Explorer/Controls/DiffEditor/DiffEditorComponent.ts
|
||||||
src/Explorer/Controls/DynamicList/DynamicList.test.ts
|
|
||||||
src/Explorer/Controls/DynamicList/DynamicListComponent.ts
|
|
||||||
src/Explorer/Controls/Editor/EditorComponent.ts
|
src/Explorer/Controls/Editor/EditorComponent.ts
|
||||||
src/Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.ts
|
|
||||||
src/Explorer/Controls/InputTypeahead/InputTypeahead.ts
|
|
||||||
src/Explorer/Controls/JsonEditor/JsonEditorComponent.ts
|
src/Explorer/Controls/JsonEditor/JsonEditorComponent.ts
|
||||||
src/Explorer/Controls/Notebook/NotebookAppMessageHandler.ts
|
|
||||||
src/Explorer/Controls/ThroughputInput/ThroughputInputComponent.ts
|
|
||||||
src/Explorer/Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3.ts
|
|
||||||
src/Explorer/Controls/Toolbar/IToolbarAction.ts
|
|
||||||
src/Explorer/Controls/Toolbar/IToolbarDisplayable.ts
|
|
||||||
src/Explorer/Controls/Toolbar/IToolbarDropDown.ts
|
|
||||||
src/Explorer/Controls/Toolbar/IToolbarItem.ts
|
|
||||||
src/Explorer/Controls/Toolbar/IToolbarSeperator.ts
|
|
||||||
src/Explorer/Controls/Toolbar/IToolbarToggle.ts
|
|
||||||
src/Explorer/Controls/Toolbar/KeyCodes.ts
|
|
||||||
src/Explorer/Controls/Toolbar/Toolbar.ts
|
|
||||||
src/Explorer/Controls/Toolbar/ToolbarAction.ts
|
|
||||||
src/Explorer/Controls/Toolbar/ToolbarDropDown.ts
|
|
||||||
src/Explorer/Controls/Toolbar/ToolbarToggle.ts
|
|
||||||
src/Explorer/Controls/Toolbar/Utilities.ts
|
|
||||||
src/Explorer/DataSamples/ContainerSampleGenerator.test.ts
|
src/Explorer/DataSamples/ContainerSampleGenerator.test.ts
|
||||||
src/Explorer/DataSamples/ContainerSampleGenerator.ts
|
src/Explorer/DataSamples/ContainerSampleGenerator.ts
|
||||||
src/Explorer/DataSamples/DataSamplesUtil.test.ts
|
src/Explorer/DataSamples/DataSamplesUtil.test.ts
|
||||||
src/Explorer/DataSamples/DataSamplesUtil.ts
|
src/Explorer/DataSamples/DataSamplesUtil.ts
|
||||||
src/Explorer/Explorer.tsx
|
|
||||||
src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.test.ts
|
src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.test.ts
|
||||||
src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.ts
|
src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.ts
|
||||||
src/Explorer/Graph/GraphExplorerComponent/D3ForceGraph.test.ts
|
src/Explorer/Graph/GraphExplorerComponent/D3ForceGraph.test.ts
|
||||||
@@ -84,11 +55,6 @@ src/Explorer/Graph/GraphExplorerComponent/GremlinClient.test.ts
|
|||||||
src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts
|
src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts
|
||||||
src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.test.ts
|
src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.test.ts
|
||||||
src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.ts
|
src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.ts
|
||||||
# src/Explorer/Graph/GraphStyleComponent/GraphStyle.test.ts
|
|
||||||
# src/Explorer/Graph/GraphStyleComponent/GraphStyleComponent.ts
|
|
||||||
|
|
||||||
src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts
|
|
||||||
src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.ts
|
|
||||||
src/Explorer/Menus/ContextMenu.ts
|
src/Explorer/Menus/ContextMenu.ts
|
||||||
src/Explorer/MostRecentActivity/MostRecentActivity.ts
|
src/Explorer/MostRecentActivity/MostRecentActivity.ts
|
||||||
src/Explorer/Notebook/NotebookClientV2.ts
|
src/Explorer/Notebook/NotebookClientV2.ts
|
||||||
@@ -105,40 +71,19 @@ src/Explorer/Notebook/NotebookContainerClient.ts
|
|||||||
src/Explorer/Notebook/NotebookContentClient.ts
|
src/Explorer/Notebook/NotebookContentClient.ts
|
||||||
src/Explorer/Notebook/NotebookContentItem.ts
|
src/Explorer/Notebook/NotebookContentItem.ts
|
||||||
src/Explorer/Notebook/NotebookUtil.ts
|
src/Explorer/Notebook/NotebookUtil.ts
|
||||||
src/Explorer/OpenActions.test.ts
|
|
||||||
src/Explorer/OpenActions.ts
|
|
||||||
src/Explorer/OpenActionsStubs.ts
|
src/Explorer/OpenActionsStubs.ts
|
||||||
src/Explorer/Panes/AddDatabasePane.ts
|
|
||||||
src/Explorer/Panes/AddDatabasePane.test.ts
|
|
||||||
src/Explorer/Panes/BrowseQueriesPane.ts
|
|
||||||
src/Explorer/Panes/ContextualPaneBase.ts
|
|
||||||
# src/Explorer/Panes/GraphStylingPane.ts
|
|
||||||
# src/Explorer/Panes/NewVertexPane.ts
|
|
||||||
src/Explorer/Panes/RenewAdHocAccessPane.ts
|
|
||||||
src/Explorer/Panes/SetupNotebooksPane.ts
|
|
||||||
src/Explorer/Panes/SwitchDirectoryPane.ts
|
|
||||||
src/Explorer/Panes/Tables/Validators/EntityPropertyNameValidator.ts
|
src/Explorer/Panes/Tables/Validators/EntityPropertyNameValidator.ts
|
||||||
src/Explorer/Panes/Tables/Validators/EntityPropertyValidationCommon.ts
|
src/Explorer/Panes/Tables/Validators/EntityPropertyValidationCommon.ts
|
||||||
src/Explorer/Panes/Tables/Validators/EntityPropertyValueValidator.ts
|
src/Explorer/Panes/Tables/Validators/EntityPropertyValueValidator.ts
|
||||||
src/Explorer/SplashScreen/SplashScreen.test.ts
|
src/Explorer/SplashScreen/SplashScreen.test.ts
|
||||||
src/Explorer/Tables/Constants.ts
|
|
||||||
src/Explorer/Tables/DataTable/CacheBase.ts
|
src/Explorer/Tables/DataTable/CacheBase.ts
|
||||||
src/Explorer/Tables/DataTable/DataTableBindingManager.ts
|
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/QueryBuilder/QueryViewModel.ts
|
|
||||||
src/Explorer/Tables/TableDataClient.ts
|
src/Explorer/Tables/TableDataClient.ts
|
||||||
src/Explorer/Tables/TableEntityProcessor.ts
|
src/Explorer/Tables/TableEntityProcessor.ts
|
||||||
src/Explorer/Tables/Utilities.ts
|
src/Explorer/Tables/Utilities.ts
|
||||||
@@ -148,170 +93,51 @@ src/Explorer/Tabs/DocumentsTab.test.ts
|
|||||||
src/Explorer/Tabs/DocumentsTab.ts
|
src/Explorer/Tabs/DocumentsTab.ts
|
||||||
src/Explorer/Tabs/GraphTab.ts
|
src/Explorer/Tabs/GraphTab.ts
|
||||||
src/Explorer/Tabs/MongoDocumentsTab.ts
|
src/Explorer/Tabs/MongoDocumentsTab.ts
|
||||||
src/Explorer/Tabs/MongoQueryTab.ts
|
|
||||||
src/Explorer/Tabs/MongoShellTab.ts
|
|
||||||
src/Explorer/Tabs/NotebookV2Tab.ts
|
|
||||||
src/Explorer/Tabs/QueryTab.test.ts
|
|
||||||
src/Explorer/Tabs/QueryTab.ts
|
|
||||||
src/Explorer/Tabs/QueryTablesTab.ts
|
|
||||||
src/Explorer/Tabs/ScriptTabBase.ts
|
|
||||||
src/Explorer/Tabs/StoredProcedureTab.ts
|
|
||||||
src/Explorer/Tabs/TabComponents.ts
|
|
||||||
src/Explorer/Tabs/TabsBase.ts
|
src/Explorer/Tabs/TabsBase.ts
|
||||||
src/Explorer/Tabs/TriggerTab.ts
|
|
||||||
src/Explorer/Tabs/UserDefinedFunctionTab.ts
|
src/Explorer/Tabs/UserDefinedFunctionTab.ts
|
||||||
src/Explorer/Tree/AccessibleVerticalList.ts
|
src/Explorer/Tree/AccessibleVerticalList.ts
|
||||||
src/Explorer/Tree/Collection.test.ts
|
|
||||||
src/Explorer/Tree/Collection.ts
|
src/Explorer/Tree/Collection.ts
|
||||||
src/Explorer/Tree/ConflictId.ts
|
src/Explorer/Tree/ConflictId.ts
|
||||||
src/Explorer/Tree/Database.ts
|
|
||||||
src/Explorer/Tree/DocumentId.ts
|
src/Explorer/Tree/DocumentId.ts
|
||||||
src/Explorer/Tree/ObjectId.ts
|
src/Explorer/Tree/ObjectId.ts
|
||||||
src/Explorer/Tree/ResourceTokenCollection.ts
|
src/Explorer/Tree/ResourceTokenCollection.ts
|
||||||
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/Tree/UserDefinedFunction.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/GitHubClient.ts
|
||||||
src/GitHub/GitHubConnector.ts
|
|
||||||
src/GitHub/GitHubContentProvider.test.ts
|
|
||||||
src/GitHub/GitHubContentProvider.ts
|
|
||||||
src/GitHub/GitHubOAuthService.ts
|
|
||||||
src/HostedExplorer.ts
|
|
||||||
src/Index.ts
|
src/Index.ts
|
||||||
src/Juno/JunoClient.test.ts
|
|
||||||
src/Juno/JunoClient.ts
|
|
||||||
src/Main.ts
|
|
||||||
src/NotebookWorkspaceManager/NotebookWorkspaceManager.ts
|
|
||||||
src/NotebookWorkspaceManager/NotebookWorkspaceResourceProviderMockClients.ts
|
|
||||||
src/Platform/Emulator/DataAccessUtility.ts
|
|
||||||
src/Platform/Emulator/ExplorerFactory.ts
|
|
||||||
src/Platform/Emulator/Main.ts
|
|
||||||
src/Platform/Emulator/NotificationsClient.ts
|
|
||||||
src/Platform/Hosted/ArmResourceUtils.ts
|
|
||||||
src/Platform/Hosted/Authorization.ts
|
src/Platform/Hosted/Authorization.ts
|
||||||
src/Platform/Hosted/DataAccessUtility.ts
|
|
||||||
src/Platform/Hosted/ExplorerFactory.ts
|
|
||||||
src/Platform/Hosted/Helpers/ConnectionStringParser.test.ts
|
|
||||||
src/Platform/Hosted/Main.ts
|
|
||||||
src/Platform/Hosted/Maint.test.ts
|
|
||||||
src/Platform/Hosted/NotificationsClient.ts
|
|
||||||
src/Platform/Portal/DataAccessUtility.ts
|
|
||||||
src/Platform/Portal/ExplorerFactory.ts
|
|
||||||
src/Platform/Portal/Main.ts
|
|
||||||
src/Platform/Portal/NotificationsClient.ts
|
|
||||||
src/PlatformType.ts
|
|
||||||
src/ReactDevTools.ts
|
src/ReactDevTools.ts
|
||||||
src/ResourceProvider/IResourceProviderClient.test.ts
|
|
||||||
src/ResourceProvider/IResourceProviderClient.ts
|
|
||||||
src/ResourceProvider/ResourceProviderClient.ts
|
|
||||||
src/ResourceProvider/ResourceProviderClientFactory.ts
|
|
||||||
src/RouteHandlers/RouteHandler.ts
|
|
||||||
src/RouteHandlers/TabRouteHandler.test.ts
|
|
||||||
src/RouteHandlers/TabRouteHandler.ts
|
|
||||||
src/Shared/Constants.ts
|
src/Shared/Constants.ts
|
||||||
src/Shared/DefaultExperienceUtility.test.ts
|
src/Shared/DefaultExperienceUtility.test.ts
|
||||||
src/Shared/DefaultExperienceUtility.ts
|
src/Shared/DefaultExperienceUtility.ts
|
||||||
src/Shared/ExplorerSettings.ts
|
|
||||||
src/Shared/PriceEstimateCalculator.ts
|
|
||||||
src/Shared/StorageUtility.test.ts
|
|
||||||
src/Shared/StorageUtility.ts
|
|
||||||
src/Shared/appInsights.ts
|
src/Shared/appInsights.ts
|
||||||
src/SparkClusterManager/ArcadiaResourceManager.ts
|
src/SparkClusterManager/ArcadiaResourceManager.ts
|
||||||
src/SparkClusterManager/SparkClusterManager.ts
|
src/SparkClusterManager/SparkClusterManager.ts
|
||||||
src/Terminal/JupyterLabAppFactory.ts
|
src/Terminal/JupyterLabAppFactory.ts
|
||||||
src/Terminal/NotebookAppContracts.d.ts
|
src/Terminal/NotebookAppContracts.d.ts
|
||||||
src/Terminal/index.ts
|
|
||||||
src/TokenProviders/PortalTokenProvider.ts
|
|
||||||
src/TokenProviders/TokenProviderFactory.ts
|
|
||||||
src/Utils/PricingUtils.test.ts
|
|
||||||
src/Utils/QueryUtils.test.ts
|
|
||||||
src/applyExplorerBindings.ts
|
src/applyExplorerBindings.ts
|
||||||
src/global.d.ts
|
src/global.d.ts
|
||||||
src/setupTests.ts
|
src/setupTests.ts
|
||||||
src/Explorer/Controls/AccessibleElement/AccessibleElement.tsx
|
|
||||||
src/Explorer/Controls/Accordion/AccordionComponent.tsx
|
|
||||||
src/Explorer/Controls/AccountSwitch/AccountSwitchComponent.test.tsx
|
|
||||||
src/Explorer/Controls/AccountSwitch/AccountSwitchComponent.tsx
|
|
||||||
src/Explorer/Controls/AccountSwitch/AccountSwitchComponentAdapter.tsx
|
|
||||||
src/Explorer/Controls/Arcadia/ArcadiaMenuPicker.tsx
|
|
||||||
src/Explorer/Controls/CollapsiblePanel/CollapsiblePanel.tsx
|
|
||||||
src/Explorer/Controls/CommandButton/CommandButtonComponent.tsx
|
|
||||||
src/Explorer/Controls/DialogReactComponent/DialogComponent.tsx
|
|
||||||
src/Explorer/Controls/DialogReactComponent/DialogComponentAdapter.tsx
|
|
||||||
src/Explorer/Controls/Directory/DefaultDirectoryDropdownComponent.test.tsx
|
|
||||||
src/Explorer/Controls/Directory/DefaultDirectoryDropdownComponent.tsx
|
|
||||||
src/Explorer/Controls/Directory/DirectoryComponentAdapter.tsx
|
|
||||||
src/Explorer/Controls/Directory/DirectoryListComponent.test.tsx
|
|
||||||
src/Explorer/Controls/Directory/DirectoryListComponent.tsx
|
|
||||||
src/Explorer/Controls/Editor/EditorReact.tsx
|
|
||||||
src/Explorer/Controls/InputTypeahead/InputTypeaheadComponent.tsx
|
src/Explorer/Controls/InputTypeahead/InputTypeaheadComponent.tsx
|
||||||
src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx
|
src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx
|
||||||
src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
|
src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
|
||||||
src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.tsx
|
|
||||||
src/NotebookViewer/NotebookViewer.tsx
|
|
||||||
src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
|
src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
|
||||||
src/Explorer/Controls/QueriesGridReactComponent/QueriesGridComponent.tsx
|
|
||||||
src/Explorer/Controls/QueriesGridReactComponent/QueriesGridComponentAdapter.tsx
|
|
||||||
src/Explorer/Controls/ResizeSensorReactComponent/ResizeSensorComponent.tsx
|
|
||||||
src/Explorer/Controls/Spark/ClusterSettingsComponent.tsx
|
|
||||||
src/Explorer/Controls/Spark/ClusterSettingsComponentAdapter.tsx
|
|
||||||
src/Explorer/Controls/Tabs/TabComponent.tsx
|
|
||||||
src/Explorer/Controls/TreeComponent/TreeComponent.test.tsx
|
|
||||||
src/Explorer/Controls/TreeComponent/TreeComponent.tsx
|
src/Explorer/Controls/TreeComponent/TreeComponent.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/EditorNeighborsComponent.tsx
|
|
||||||
src/Explorer/Graph/GraphExplorerComponent/EditorNodePropertiesComponent.test.tsx
|
|
||||||
src/Explorer/Graph/GraphExplorerComponent/EditorNodePropertiesComponent.tsx
|
|
||||||
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
|
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
|
|
||||||
src/Explorer/Graph/GraphExplorerComponent/GraphExplorerAdapter.tsx
|
|
||||||
src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx
|
|
||||||
src/Explorer/Graph/GraphExplorerComponent/LeftPaneComponent.tsx
|
|
||||||
src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx
|
|
||||||
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.test.tsx
|
|
||||||
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.tsx
|
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/QueryContainerComponent.tsx
|
|
||||||
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNeighborsComponent.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/Menus/NotificationConsole/NotificationConsoleComponent.test.tsx
|
|
||||||
src/Explorer/Notebook/NotebookComponent/NotebookComponent.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/file/index.tsx
|
|
||||||
src/Explorer/Notebook/NotebookComponent/contents/file/text-file.tsx
|
|
||||||
src/Explorer/Notebook/NotebookComponent/contents/index.tsx
|
src/Explorer/Notebook/NotebookComponent/contents/index.tsx
|
||||||
src/Explorer/Notebook/NotebookRenderer/AzureTheme.tsx
|
|
||||||
src/Explorer/Notebook/NotebookRenderer/NotebookReadOnlyRenderer.tsx
|
|
||||||
src/Explorer/Notebook/NotebookRenderer/NotebookRenderer.tsx
|
src/Explorer/Notebook/NotebookRenderer/NotebookRenderer.tsx
|
||||||
src/Explorer/Notebook/NotebookRenderer/Prompt.tsx
|
|
||||||
src/Explorer/Notebook/NotebookRenderer/PromptContent.tsx
|
|
||||||
src/Explorer/Notebook/NotebookRenderer/StatusBar.test.tsx
|
|
||||||
src/Explorer/Notebook/NotebookRenderer/StatusBar.tsx
|
|
||||||
src/Explorer/Notebook/NotebookRenderer/Toolbar.tsx
|
|
||||||
src/Explorer/Notebook/NotebookRenderer/decorators/CellCreator.tsx
|
|
||||||
src/Explorer/Notebook/NotebookRenderer/decorators/CellLabeler.tsx
|
|
||||||
src/Explorer/Notebook/NotebookRenderer/decorators/HoverableCell.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
|
||||||
src/Explorer/Notebook/temp/inputs/connected-editors/codemirror.tsx
|
src/Explorer/Notebook/temp/inputs/connected-editors/codemirror.tsx
|
||||||
src/Explorer/Notebook/temp/inputs/editor.tsx
|
|
||||||
src/Explorer/Notebook/temp/markdown-cell.tsx
|
|
||||||
src/Explorer/Notebook/temp/source.tsx
|
|
||||||
src/Explorer/Notebook/temp/syntax-highlighter/index.tsx
|
|
||||||
src/Explorer/SplashScreen/SplashScreen.tsx
|
|
||||||
src/Explorer/Tabs/GalleryTab.tsx
|
|
||||||
src/Explorer/Tabs/NotebookViewerTab.tsx
|
|
||||||
src/Explorer/Tabs/TerminalTab.tsx
|
|
||||||
src/Explorer/Tree/ResourceTreeAdapter.tsx
|
src/Explorer/Tree/ResourceTreeAdapter.tsx
|
||||||
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.tsx
|
|
||||||
src/GalleryViewer/Cards/GalleryCardComponent.tsx
|
|
||||||
src/GalleryViewer/GalleryViewer.tsx
|
|
||||||
src/GalleryViewer/GalleryViewerComponent.tsx
|
|
||||||
__mocks__/monaco-editor.ts
|
__mocks__/monaco-editor.ts
|
||||||
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx
|
src/Explorer/Tree/ResourceTree.tsx
|
||||||
@@ -39,7 +39,6 @@ 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",
|
||||||
|
|||||||
6
.github/workflows/ci.yml
vendored
@@ -92,11 +92,11 @@ jobs:
|
|||||||
name: dist
|
name: dist
|
||||||
path: dist/
|
path: dist/
|
||||||
- name: Upload build to preview blob storage
|
- name: Upload build to preview blob storage
|
||||||
run: az storage blob upload-batch -d '$web' -s 'dist' --account-name cosmosexplorerpreview --subscription cosmosdb-portalteam-generaldemo --destination-path "${{github.event.pull_request.head.sha}}" --account-key="${PREVIEW_STORAGE_KEY}"
|
run: az storage blob upload-batch -d '$web' -s 'dist' --account-name cosmosexplorerpreview --subscription cosmosdb-portalteam-generaldemo --destination-path "${{github.event.pull_request.head.sha || github.sha}}" --account-key="${PREVIEW_STORAGE_KEY}"
|
||||||
env:
|
env:
|
||||||
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
||||||
- name: Upload preview config to blob storage
|
- name: Upload preview config to blob storage
|
||||||
run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --subscription cosmosdb-portalteam-generaldemo --name "${{github.event.pull_request.head.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}"
|
run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --subscription cosmosdb-portalteam-generaldemo --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}"
|
||||||
env:
|
env:
|
||||||
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
||||||
endtoendemulator:
|
endtoendemulator:
|
||||||
@@ -143,7 +143,7 @@ jobs:
|
|||||||
- ./test/mongo/container.spec.ts
|
- ./test/mongo/container.spec.ts
|
||||||
- ./test/mongo/container32.spec.ts
|
- ./test/mongo/container32.spec.ts
|
||||||
- ./test/selfServe/selfServeExample.spec.ts
|
- ./test/selfServe/selfServeExample.spec.ts
|
||||||
- ./test/notebooks/upload.spec.ts
|
# - ./test/notebooks/upload.spec.ts // TEMP disabled since notebooks service is off
|
||||||
- ./test/sql/resourceToken.spec.ts
|
- ./test/sql/resourceToken.spec.ts
|
||||||
- ./test/tables/container.spec.ts
|
- ./test/tables/container.spec.ts
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
2
.github/workflows/cleanup.yml
vendored
@@ -7,7 +7,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
# Once every hour
|
# Once every hour
|
||||||
- cron: "0 * * * *"
|
- cron: "0 15 * * *"
|
||||||
|
|
||||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
3
.vscode/settings.json
vendored
@@ -22,5 +22,6 @@
|
|||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": true,
|
"source.fixAll.eslint": true,
|
||||||
"source.organizeImports": true
|
"source.organizeImports": true
|
||||||
}
|
},
|
||||||
|
"typescript.preferences.importModuleSpecifier": "non-relative"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
{
|
{
|
||||||
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com",
|
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com"
|
||||||
"enableSchemaAnalyzer": true
|
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 842 B After Width: | Height: | Size: 842 B |
|
Before Width: | Height: | Size: 371 B After Width: | Height: | Size: 371 B |
@@ -1,16 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg
|
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
version="1.1"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<path
|
<path
|
||||||
d="M14,2.691l-5.301,5.309l5.301,5.309l-0.691,0.691l-5.309,-5.301l-5.309,5.301l-0.691,-0.691l5.301,-5.309l-5.301,-5.309l0.691,-0.691l5.309,5.301l5.309,-5.301l0.691,0.691Z"
|
d="M14,2.691l-5.301,5.309l5.301,5.309l-0.691,0.691l-5.309,-5.301l-5.309,5.301l-0.691,-0.691l5.301,-5.309l-5.301,-5.309l0.691,-0.691l5.309,5.301l5.309,-5.301l0.691,0.691Z"
|
||||||
transform="scale(0.5)"
|
transform="scale(0.5)" fill="#000" stroke="#000">
|
||||||
fill="#000"
|
|
||||||
stroke="#CCC"
|
|
||||||
>
|
|
||||||
</path>
|
</path>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 503 B After Width: | Height: | Size: 449 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -37,8 +37,8 @@ module.exports = {
|
|||||||
global: {
|
global: {
|
||||||
branches: 25,
|
branches: 25,
|
||||||
functions: 25,
|
functions: 25,
|
||||||
lines: 30,
|
lines: 29,
|
||||||
statements: 30,
|
statements: 29,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -129,6 +129,8 @@ 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: {},
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
@DataExplorerFont: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
|
@DataExplorerFont: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
|
||||||
@SemiboldFont: "Segoe UI Semibold", "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
|
@SemiboldFont: "Segoe UI Semibold", "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
|
||||||
|
@GrayScale: "grayscale()";
|
||||||
|
|
||||||
@xSmallFontSize: 4px;
|
@xSmallFontSize: 4px;
|
||||||
@smallFontSize: 8px;
|
@smallFontSize: 8px;
|
||||||
|
|||||||
@@ -724,45 +724,24 @@ execute-sproc-params-pane {
|
|||||||
|
|
||||||
.results-container,
|
.results-container,
|
||||||
.errors-container {
|
.errors-container {
|
||||||
padding: @MediumSpace 0px 0px @MediumSpace;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
.flex-display();
|
.flex-display();
|
||||||
.flex-direction();
|
.flex-direction();
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.toggles {
|
|
||||||
height: @ToggleHeight;
|
|
||||||
width: @ToggleWidth;
|
|
||||||
margin-left: @MediumSpace;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab {
|
|
||||||
margin-right: @MediumSpace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggleSwitch {
|
|
||||||
.toggleSwitch();
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectedToggle {
|
|
||||||
.selectedToggle();
|
|
||||||
}
|
|
||||||
|
|
||||||
.unselectedToggle {
|
|
||||||
.unselectedToggle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.enterInputParameters {
|
.enterInputParameters {
|
||||||
padding: @LargeSpace @MediumSpace;
|
padding: @LargeSpace @MediumSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div[role="tabpanel"] {
|
||||||
|
height: 100%;
|
||||||
|
padding-bottom: 50px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.errors-container {
|
.errors-container {
|
||||||
padding-left: (2 * @MediumSpace);
|
padding-left: (2 * @MediumSpace);
|
||||||
|
padding: @MediumSpace 0px 0px @MediumSpace;
|
||||||
.errors-header {
|
.errors-header {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: @DefaultFontSize;
|
font-size: @DefaultFontSize;
|
||||||
@@ -2378,6 +2357,8 @@ a:link {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
min-height: 300px;
|
||||||
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs {
|
.tabs {
|
||||||
@@ -2853,6 +2834,8 @@ a:link {
|
|||||||
|
|
||||||
#explorerNotificationConsole {
|
#explorerNotificationConsole {
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: clip;
|
||||||
}
|
}
|
||||||
|
|
||||||
.uniqueIndexesContainer {
|
.uniqueIndexesContainer {
|
||||||
@@ -3085,3 +3068,14 @@ settings-pane {
|
|||||||
padding-left: @SmallSpace;
|
padding-left: @SmallSpace;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.hiddenMain {
|
||||||
|
display: none;
|
||||||
|
height: 0px;
|
||||||
|
}
|
||||||
|
.spinner {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
background: white;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|||||||
@@ -201,3 +201,11 @@
|
|||||||
.migration:disabled {
|
.migration:disabled {
|
||||||
background-color: #ccc;
|
background-color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.trigger-field {
|
||||||
|
width: 40%;
|
||||||
|
margin-top: 10px
|
||||||
|
}
|
||||||
|
.trigger-form {
|
||||||
|
padding: 10px 30px 10px 30px;
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
.dataResourceTree {
|
.dataResourceTree {
|
||||||
margin-left: @MediumSpace;
|
margin-left: @MediumSpace;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
.databaseHeader {
|
.databaseHeader {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -18,6 +19,10 @@
|
|||||||
.notebookHeader {
|
.notebookHeader {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.clickDisabled {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
@import "./Common/Constants";
|
@import "./Common/Constants";
|
||||||
|
|
||||||
|
|
||||||
.resourceTree {
|
.resourceTree {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
@@ -45,20 +44,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.contextmenushowing {
|
.contextmenushowing {
|
||||||
background-color: #EEE;
|
background-color: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collectionstree {
|
.collectionstree {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: @DefaultSpace;
|
margin-top: @DefaultSpace;
|
||||||
|
|
||||||
|
|
||||||
.databaseList {
|
.databaseList {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
|
|
||||||
.collectionList {
|
.collectionList {
|
||||||
padding-left:(2 * @MediumSpace);
|
padding-left: (2 * @MediumSpace);
|
||||||
}
|
}
|
||||||
|
|
||||||
.collectionChildList {
|
.collectionChildList {
|
||||||
@@ -84,7 +82,7 @@
|
|||||||
left: 0px;
|
left: 0px;
|
||||||
float: right;
|
float: right;
|
||||||
display: none;
|
display: none;
|
||||||
padding-left: 6px!important;
|
padding-left: 6px !important;
|
||||||
line-height: @TreeLineHeight;
|
line-height: @TreeLineHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +189,7 @@ img.collectionsTreeCollapseExpand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.expanded::before {
|
.expanded::before {
|
||||||
content: '\23F7';
|
content: "\23F7";
|
||||||
margin-left: 0px;
|
margin-left: 0px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
@@ -210,7 +208,7 @@ img.collectionsTreeCollapseExpand {
|
|||||||
transform: rotate(-90deg) translateX(-100%);
|
transform: rotate(-90deg) translateX(-100%);
|
||||||
-webkit-transform: rotate(-90deg) translateX(-100%);
|
-webkit-transform: rotate(-90deg) translateX(-100%);
|
||||||
-ms-transform: rotate(-90deg) translateX(-100%);
|
-ms-transform: rotate(-90deg) translateX(-100%);
|
||||||
border-bottom: 1px solid #CCC;
|
border-bottom: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-nav-img {
|
.main-nav-img {
|
||||||
@@ -256,11 +254,11 @@ ul.nav {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlight:hover>.contextmenubutton {
|
.highlight:hover > .contextmenubutton {
|
||||||
display: unset;
|
display: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlight:hover>.contextmenubutton::after {
|
.highlight:hover > .contextmenubutton::after {
|
||||||
content: "\2026";
|
content: "\2026";
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|||||||
4774
package-lock.json
generated
47
package.json
@@ -22,7 +22,7 @@
|
|||||||
"@nteract/data-explorer": "8.0.3",
|
"@nteract/data-explorer": "8.0.3",
|
||||||
"@nteract/directory-listing": "2.0.6",
|
"@nteract/directory-listing": "2.0.6",
|
||||||
"@nteract/dropdown-menu": "1.0.1",
|
"@nteract/dropdown-menu": "1.0.1",
|
||||||
"@nteract/editor": "10.1.2",
|
"@nteract/editor": "10.1.12",
|
||||||
"@nteract/fixtures": "2.3.0",
|
"@nteract/fixtures": "2.3.0",
|
||||||
"@nteract/iron-icons": "1.0.0",
|
"@nteract/iron-icons": "1.0.0",
|
||||||
"@nteract/jupyter-widgets": "2.0.0",
|
"@nteract/jupyter-widgets": "2.0.0",
|
||||||
@@ -42,14 +42,15 @@
|
|||||||
"@octokit/rest": "17.9.2",
|
"@octokit/rest": "17.9.2",
|
||||||
"@phosphor/widgets": "1.9.3",
|
"@phosphor/widgets": "1.9.3",
|
||||||
"@testing-library/jest-dom": "5.11.9",
|
"@testing-library/jest-dom": "5.11.9",
|
||||||
|
"@types/lodash": "4.14.171",
|
||||||
"@types/mkdirp": "1.0.1",
|
"@types/mkdirp": "1.0.1",
|
||||||
"@types/node-fetch": "2.5.7",
|
"@types/node-fetch": "2.5.7",
|
||||||
"applicationinsights": "1.8.0",
|
"applicationinsights": "1.8.0",
|
||||||
"bootstrap": "3.4.1",
|
"bootstrap": "3.4.1",
|
||||||
"canvas": "file:./canvas",
|
"canvas": "file:./canvas",
|
||||||
"clean-webpack-plugin": "0.1.19",
|
"clean-webpack-plugin": "3.0.0",
|
||||||
"clipboard-copy": "4.0.1",
|
"clipboard-copy": "4.0.1",
|
||||||
"copy-webpack-plugin": "6.0.2",
|
"copy-webpack-plugin": "9.0.1",
|
||||||
"crossroads": "0.12.2",
|
"crossroads": "0.12.2",
|
||||||
"css-element-queries": "1.1.1",
|
"css-element-queries": "1.1.1",
|
||||||
"d3": "6.1.1",
|
"d3": "6.1.1",
|
||||||
@@ -80,15 +81,16 @@
|
|||||||
"plotly.js-cartesian-dist-min": "1.52.3",
|
"plotly.js-cartesian-dist-min": "1.52.3",
|
||||||
"post-robot": "10.0.42",
|
"post-robot": "10.0.42",
|
||||||
"q": "1.5.1",
|
"q": "1.5.1",
|
||||||
"react": "16.13.1",
|
"react": "16.14.0",
|
||||||
"react-animate-height": "2.0.8",
|
"react-animate-height": "2.0.8",
|
||||||
"react-dnd": "9.4.0",
|
"react-dnd": "14.0.2",
|
||||||
"react-dnd-html5-backend": "9.4.0",
|
"react-dnd-html5-backend": "14.0.0",
|
||||||
"react-dom": "16.13.1",
|
"react-dom": "16.13.1",
|
||||||
"react-hotkeys": "2.0.0",
|
"react-hotkeys": "2.0.0",
|
||||||
"react-i18next": "11.8.5",
|
"react-i18next": "11.8.5",
|
||||||
"react-notification-system": "0.2.17",
|
"react-notification-system": "0.2.17",
|
||||||
"react-redux": "7.1.3",
|
"react-redux": "7.1.3",
|
||||||
|
"react-splitter-layout": "4.0.0",
|
||||||
"redux": "4.0.4",
|
"redux": "4.0.4",
|
||||||
"reflect-metadata": "0.1.13",
|
"reflect-metadata": "0.1.13",
|
||||||
"rx-jupyter": "5.5.12",
|
"rx-jupyter": "5.5.12",
|
||||||
@@ -96,7 +98,7 @@
|
|||||||
"sanitize-html": "2.3.3",
|
"sanitize-html": "2.3.3",
|
||||||
"styled-components": "4.3.2",
|
"styled-components": "4.3.2",
|
||||||
"swr": "0.4.0",
|
"swr": "0.4.0",
|
||||||
"terser-webpack-plugin": "3.1.0",
|
"terser-webpack-plugin": "5.1.4",
|
||||||
"underscore": "1.9.1",
|
"underscore": "1.9.1",
|
||||||
"utility-types": "3.10.0",
|
"utility-types": "3.10.0",
|
||||||
"zustand": "3.5.0"
|
"zustand": "3.5.0"
|
||||||
@@ -123,12 +125,14 @@
|
|||||||
"@types/react-dom": "17.0.3",
|
"@types/react-dom": "17.0.3",
|
||||||
"@types/react-notification-system": "0.2.39",
|
"@types/react-notification-system": "0.2.39",
|
||||||
"@types/react-redux": "7.1.7",
|
"@types/react-redux": "7.1.7",
|
||||||
|
"@types/react-splitter-layout": "3.0.1",
|
||||||
"@types/sanitize-html": "1.27.2",
|
"@types/sanitize-html": "1.27.2",
|
||||||
"@types/sinon": "2.3.3",
|
"@types/sinon": "2.3.3",
|
||||||
"@types/styled-components": "5.1.1",
|
"@types/styled-components": "5.1.1",
|
||||||
"@types/underscore": "1.7.36",
|
"@types/underscore": "1.7.36",
|
||||||
"@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",
|
||||||
"babel-jest": "24.9.0",
|
"babel-jest": "24.9.0",
|
||||||
"babel-loader": "8.1.0",
|
"babel-loader": "8.1.0",
|
||||||
"buffer": "5.1.0",
|
"buffer": "5.1.0",
|
||||||
@@ -150,44 +154,45 @@
|
|||||||
"html-inline-css-webpack-plugin": "1.11.0",
|
"html-inline-css-webpack-plugin": "1.11.0",
|
||||||
"html-loader": "0.5.5",
|
"html-loader": "0.5.5",
|
||||||
"html-loader-jest": "0.2.1",
|
"html-loader-jest": "0.2.1",
|
||||||
"html-webpack-plugin": "4.5.2",
|
"html-webpack-plugin": "5.3.2",
|
||||||
"jest": "25.5.4",
|
"jest": "26.6.3",
|
||||||
"jest-canvas-mock": "2.1.0",
|
"jest-canvas-mock": "2.3.1",
|
||||||
"jest-playwright-preset": "1.5.1",
|
"jest-playwright-preset": "1.5.1",
|
||||||
"jest-trx-results-processor": "0.0.7",
|
"jest-trx-results-processor": "0.0.7",
|
||||||
"less": "3.8.1",
|
"less": "3.8.1",
|
||||||
"less-loader": "4.1.0",
|
"less-loader": "4.1.0",
|
||||||
"less-vars-loader": "1.1.0",
|
"less-vars-loader": "1.1.0",
|
||||||
"mini-css-extract-plugin": "0.4.3",
|
"mini-css-extract-plugin": "2.1.0",
|
||||||
"monaco-editor-webpack-plugin": "1.7.0",
|
"monaco-editor-webpack-plugin": "1.7.0",
|
||||||
"node-fetch": "2.6.1",
|
"node-fetch": "2.6.1",
|
||||||
"playwright": "1.10.0",
|
"playwright": "1.13.0",
|
||||||
"prettier": "2.2.1",
|
"prettier": "2.2.1",
|
||||||
|
"process": "0.11.10",
|
||||||
"raw-loader": "0.5.1",
|
"raw-loader": "0.5.1",
|
||||||
"react-dev-utils": "11.0.4",
|
"react-dev-utils": "11.0.4",
|
||||||
"rimraf": "3.0.0",
|
"rimraf": "3.0.0",
|
||||||
"sinon": "3.2.1",
|
"sinon": "3.2.1",
|
||||||
"style-loader": "0.23.0",
|
"style-loader": "0.23.0",
|
||||||
"ts-loader": "6.2.2",
|
"ts-loader": "9.2.4",
|
||||||
"tslint": "5.11.0",
|
"tslint": "5.11.0",
|
||||||
"tslint-microsoft-contrib": "6.0.0",
|
"tslint-microsoft-contrib": "6.0.0",
|
||||||
"typedoc": "0.20.36",
|
"typedoc": "0.20.36",
|
||||||
"typescript": "4.2.4",
|
"typescript": "4.3.4",
|
||||||
"url-loader": "1.1.1",
|
"url-loader": "1.1.1",
|
||||||
"wait-on": "4.0.2",
|
"wait-on": "4.0.2",
|
||||||
"webpack": "4.46.0",
|
"webpack": "5.47.0",
|
||||||
"webpack-bundle-analyzer": "3.6.1",
|
"webpack-bundle-analyzer": "4.4.2",
|
||||||
"webpack-cli": "3.3.10",
|
"webpack-cli": "4.7.2",
|
||||||
"webpack-dev-server": "3.11.0"
|
"webpack-dev-server": "3.11.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node --max-old-space-size=10196 node_modules/webpack-dev-server/bin/webpack-dev-server.js",
|
"start": "webpack serve --mode development",
|
||||||
"dev": "echo \"WARNING: npm run dev has been deprecated\" && npm run build",
|
"dev": "echo \"WARNING: npm run dev has been deprecated\" && npm run build",
|
||||||
"build:dataExplorer:ci": "npm run build:ci",
|
"build:dataExplorer:ci": "npm run build:ci",
|
||||||
"build": "npm run format:check && npm run lint && npm run compile && npm run compile:strict && npm run pack:prod && npm run copyToConsumers",
|
"build": "npm run format:check && npm run lint && npm run compile && npm run compile:strict && npm run pack:prod && npm run copyToConsumers",
|
||||||
"build:ci": "npm run format:check && npm run lint && npm run compile && npm run compile:strict && npm run pack:fast",
|
"build:ci": "npm run format:check && npm run lint && npm run compile && npm run compile:strict && npm run pack:fast",
|
||||||
"pack:prod": "node --max_old_space_size=10196 ./node_modules/webpack/bin/webpack.js --mode production",
|
"pack:prod": "webpack --mode production",
|
||||||
"pack:fast": "node --max_old_space_size=10196 ./node_modules/webpack/bin/webpack.js --mode development --progress",
|
"pack:fast": "webpack --mode development --progress",
|
||||||
"copyToConsumers": "node copyToConsumers",
|
"copyToConsumers": "node copyToConsumers",
|
||||||
"test": "rimraf coverage && jest",
|
"test": "rimraf coverage && jest",
|
||||||
"test:e2e": "jest -c ./jest.config.playwright.js --detectOpenHandles",
|
"test:e2e": "jest -c ./jest.config.playwright.js --detectOpenHandles",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"PROXY_PATH": "/proxy"
|
"PROXY_PATH": "/proxy",
|
||||||
|
"msalRedirectURI": "https://cosmos-explorer-preview.azurewebsites.net/"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,17 @@ app.get("/pull/:pr(\\d+)", (req, res) => {
|
|||||||
})
|
})
|
||||||
.catch(() => res.sendStatus(500));
|
.catch(() => res.sendStatus(500));
|
||||||
});
|
});
|
||||||
|
app.get("/", (req, res) => {
|
||||||
|
fetch("https://api.github.com/repos/Azure/cosmos-explorer/branches/master")
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then(({ commit: { sha } }) => {
|
||||||
|
const explorer = new URL(
|
||||||
|
"https://cosmos-explorer-preview.azurewebsites.net/commit/" + sha + "/hostedExplorer.html"
|
||||||
|
);
|
||||||
|
return res.redirect(explorer.href);
|
||||||
|
})
|
||||||
|
.catch(() => res.sendStatus(500));
|
||||||
|
});
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Example app listening on port: ${port}`);
|
console.log(`Example app listening on port: ${port}`);
|
||||||
|
|||||||
55
src/Common/CollapsedResourceTree.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
|
||||||
|
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
|
||||||
|
import { userContext } from "../UserContext";
|
||||||
|
import { NormalizedEventKey } from "./Constants";
|
||||||
|
|
||||||
|
export interface CollapsedResourceTreeProps {
|
||||||
|
toggleLeftPaneExpanded: () => void;
|
||||||
|
isLeftPaneExpanded: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps> = ({
|
||||||
|
toggleLeftPaneExpanded,
|
||||||
|
isLeftPaneExpanded,
|
||||||
|
}: CollapsedResourceTreeProps): JSX.Element => {
|
||||||
|
const focusButton = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (focusButton.current) {
|
||||||
|
focusButton.current.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const onKeyPressToggleLeftPaneExpanded = (event: React.KeyboardEvent) => {
|
||||||
|
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
|
||||||
|
toggleLeftPaneExpanded();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id="mini" className={!isLeftPaneExpanded ? "mini toggle-mini" : "hiddenMain"}>
|
||||||
|
<div className="main-nav nav">
|
||||||
|
<ul className="nav">
|
||||||
|
<li
|
||||||
|
className="resourceTreeCollapse"
|
||||||
|
id="collapseToggleLeftPaneButton"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label="Expand Tree"
|
||||||
|
onClick={toggleLeftPaneExpanded}
|
||||||
|
onKeyPress={onKeyPressToggleLeftPaneExpanded}
|
||||||
|
ref={focusButton}
|
||||||
|
>
|
||||||
|
<span className="leftarrowCollapsed">
|
||||||
|
<img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" />
|
||||||
|
</span>
|
||||||
|
<span className="collectionCollapsed">
|
||||||
|
<span>{userContext.apiType} API</span>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -94,7 +94,9 @@ export class Flights {
|
|||||||
public static readonly MongoIndexEditor = "mongoindexeditor";
|
public static readonly MongoIndexEditor = "mongoindexeditor";
|
||||||
public static readonly MongoIndexing = "mongoindexing";
|
public static readonly MongoIndexing = "mongoindexing";
|
||||||
public static readonly AutoscaleTest = "autoscaletest";
|
public static readonly AutoscaleTest = "autoscaletest";
|
||||||
public static readonly SchemaAnalyzer = "schemaanalyzer";
|
public static readonly PartitionKeyTest = "partitionkeytest";
|
||||||
|
public static readonly PKPartitionKeyTest = "pkpartitionkeytest";
|
||||||
|
public static readonly Phoenix = "phoenix";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AfecFeatures {
|
export class AfecFeatures {
|
||||||
@@ -158,16 +160,6 @@ export class DocumentsGridMetrics {
|
|||||||
public static DocumentEditorMaxWidthRatio: number = 0.4;
|
public static DocumentEditorMaxWidthRatio: number = 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExplorerMetrics {
|
|
||||||
public static SplitterMinWidth: number = 240;
|
|
||||||
public static SplitterMaxWidth: number = 400;
|
|
||||||
public static CollapsedResourceTreeWidth: number = 36;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SplitterMetrics {
|
|
||||||
public static CollapsedPositionLeft: number = ExplorerMetrics.CollapsedResourceTreeWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Areas {
|
export class Areas {
|
||||||
public static ResourceTree: string = "Resource Tree";
|
public static ResourceTree: string = "Resource Tree";
|
||||||
public static ContextualPane: string = "Contextual Pane";
|
public static ContextualPane: string = "Contextual Pane";
|
||||||
@@ -346,6 +338,14 @@ export enum ConflictOperationType {
|
|||||||
Delete = "delete",
|
Delete = "delete",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ConnectionStatusType {
|
||||||
|
Connect = "Connect",
|
||||||
|
Connecting = "Connecting",
|
||||||
|
Connected = "Connected",
|
||||||
|
Failed = "Connection Failed",
|
||||||
|
ReConnect = "Reconnect",
|
||||||
|
}
|
||||||
|
|
||||||
export const EmulatorMasterKey =
|
export const EmulatorMasterKey =
|
||||||
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
|
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
|
||||||
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
|
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
|
||||||
@@ -355,10 +355,32 @@ 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 = 5000;
|
public static readonly heartbeatDelayMs = 60000;
|
||||||
public static readonly kernelRestartInitialDelayMs = 1000;
|
public static readonly kernelRestartInitialDelayMs = 1000;
|
||||||
public static readonly kernelRestartMaxDelayMs = 20000;
|
public static readonly kernelRestartMaxDelayMs = 20000;
|
||||||
public static readonly autoSaveIntervalMs = 120000;
|
public static readonly autoSaveIntervalMs = 120000;
|
||||||
|
public static readonly memoryGuageToGB = 1048576;
|
||||||
|
public static readonly temporarilyDownMsg = "Notebooks is currently not available. We are working on it.";
|
||||||
|
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.";
|
||||||
|
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.";
|
||||||
|
public static saveNotebookModalTitle = "Save Notebook in temporary workspace";
|
||||||
|
public static saveNotebookModalContent =
|
||||||
|
"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 newNotebookUploadModalTitle = "Upload Notebook in temporary workspace";
|
||||||
|
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.";
|
||||||
|
public static newNotebookModalContent2 =
|
||||||
|
"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 galleryNotebookDownloadContent1 =
|
||||||
|
"To download, run, and make changes to this sample notebook, a temporary workspace will be created. When the session expires, any notebooks in the workspace will be removed.";
|
||||||
|
public static galleryNotebookDownloadContent2 =
|
||||||
|
"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 cosmosNotebookHomePageUrl = "https://aka.ms/cosmos-notebooks-limits";
|
||||||
|
public static cosmosNotebookGitDocumentationUrl = "https://aka.ms/cosmos-notebooks-github";
|
||||||
|
public static learnMore = "Learn more.";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SparkLibrary {
|
export class SparkLibrary {
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export function client(): Cosmos.CosmosClient {
|
|||||||
if (_client) return _client;
|
if (_client) return _client;
|
||||||
const options: Cosmos.CosmosClientOptions = {
|
const options: Cosmos.CosmosClientOptions = {
|
||||||
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
|
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
|
||||||
...(!userContext.features.enableAadDataPlane && { key: userContext.masterKey }),
|
key: userContext.masterKey,
|
||||||
tokenProvider,
|
tokenProvider,
|
||||||
connectionPolicy: {
|
connectionPolicy: {
|
||||||
enableEndpointDiscovery: false,
|
enableEndpointDiscovery: false,
|
||||||
|
|||||||
17
src/Common/DatabaseAccountUtility.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { userContext } from "../UserContext";
|
||||||
|
|
||||||
|
function isVirtualNetworkFilterEnabled() {
|
||||||
|
return userContext.databaseAccount?.properties?.isVirtualNetworkFilterEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isIpRulesEnabled() {
|
||||||
|
return userContext.databaseAccount?.properties?.ipRules?.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPrivateEndpointConnectionsEnabled() {
|
||||||
|
return userContext.databaseAccount?.properties?.privateEndpointConnections?.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPublicInternetAccessAllowed(): boolean {
|
||||||
|
return !isVirtualNetworkFilterEnabled() && !isIpRulesEnabled() && !isPrivateEndpointConnectionsEnabled();
|
||||||
|
}
|
||||||
@@ -32,7 +32,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
|
|||||||
<DatePicker
|
<DatePicker
|
||||||
className="addEntityDatePicker"
|
className="addEntityDatePicker"
|
||||||
placeholder={entityValuePlaceholder}
|
placeholder={entityValuePlaceholder}
|
||||||
value={entityValue && new Date(entityValue)}
|
value={entityValue ? new Date(entityValue) : new Date()}
|
||||||
ariaLabel={entityValuePlaceholder}
|
ariaLabel={entityValuePlaceholder}
|
||||||
onSelectDate={onSelectDate}
|
onSelectDate={onSelectDate}
|
||||||
disabled={isEntityValueDisable}
|
disabled={isEntityValueDisable}
|
||||||
@@ -59,7 +59,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
|
|||||||
disabled={isEntityValueDisable}
|
disabled={isEntityValueDisable}
|
||||||
type={entityValueType}
|
type={entityValueType}
|
||||||
placeholder={entityValuePlaceholder}
|
placeholder={entityValuePlaceholder}
|
||||||
value={typeof entityValue === "string" && entityValue}
|
value={typeof entityValue === "string" ? entityValue : ""}
|
||||||
onChange={onEntityValueChange}
|
onChange={onEntityValueChange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as HeadersUtility from "./HeadersUtility";
|
import * as ExplorerSettings from "../Shared/ExplorerSettings";
|
||||||
import { ExplorerSettings } from "../Shared/ExplorerSettings";
|
|
||||||
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
||||||
|
import * as HeadersUtility from "./HeadersUtility";
|
||||||
|
|
||||||
describe("Headers Utility", () => {
|
describe("Headers Utility", () => {
|
||||||
describe("shouldEnableCrossPartitionKeyForResourceWithPartitionKey()", () => {
|
describe("shouldEnableCrossPartitionKeyForResourceWithPartitionKey()", () => {
|
||||||
|
|||||||
@@ -3,9 +3,16 @@ import { resetConfigContext, updateConfigContext } from "../ConfigContext";
|
|||||||
import { DatabaseAccount } from "../Contracts/DataModels";
|
import { DatabaseAccount } from "../Contracts/DataModels";
|
||||||
import { Collection } from "../Contracts/ViewModels";
|
import { Collection } from "../Contracts/ViewModels";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
|
import { extractFeatures } from "../Platform/Hosted/extractFeatures";
|
||||||
import { updateUserContext } from "../UserContext";
|
import { updateUserContext } from "../UserContext";
|
||||||
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
|
import {
|
||||||
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
|
deleteDocument,
|
||||||
|
getEndpoint,
|
||||||
|
getFeatureEndpointOrDefault,
|
||||||
|
queryDocuments,
|
||||||
|
readDocument,
|
||||||
|
updateDocument,
|
||||||
|
} from "./MongoProxyClient";
|
||||||
|
|
||||||
const databaseId = "testDB";
|
const databaseId = "testDB";
|
||||||
|
|
||||||
@@ -247,4 +254,31 @@ describe("MongoProxyClient", () => {
|
|||||||
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");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe("getFeatureEndpointOrDefault", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
resetConfigContext();
|
||||||
|
updateConfigContext({
|
||||||
|
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
||||||
|
});
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
"feature.mongoProxyEndpoint": "https://localhost:12901",
|
||||||
|
"feature.mongoProxyAPIs": "readDocument|createDocument",
|
||||||
|
});
|
||||||
|
const features = extractFeatures(params);
|
||||||
|
updateUserContext({
|
||||||
|
authType: AuthType.AAD,
|
||||||
|
features: features,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns a local endpoint", () => {
|
||||||
|
const endpoint = getFeatureEndpointOrDefault("readDocument");
|
||||||
|
expect(endpoint).toEqual("https://localhost:12901/api/mongo/explorer");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns a production endpoint", () => {
|
||||||
|
const endpoint = getFeatureEndpointOrDefault("deleteDocument");
|
||||||
|
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/mongo/explorer");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import * as DataModels from "../Contracts/DataModels";
|
|||||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
import { Collection } from "../Contracts/ViewModels";
|
import { Collection } from "../Contracts/ViewModels";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
|
import { hasFlag } from "../Platform/Hosted/extractFeatures";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||||
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
||||||
@@ -78,7 +79,7 @@ export function queryDocuments(
|
|||||||
: "",
|
: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint() || "";
|
const endpoint = getFeatureEndpointOrDefault("resourcelist") || "";
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
...defaultHeaders,
|
...defaultHeaders,
|
||||||
@@ -111,7 +112,7 @@ export function queryDocuments(
|
|||||||
headers: response.headers,
|
headers: response.headers,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
errorHandling(response, "querying documents", params);
|
await errorHandling(response, "querying documents", params);
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -141,7 +142,8 @@ export function readDocument(
|
|||||||
: "",
|
: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint();
|
const endpoint = getFeatureEndpointOrDefault("readDocument");
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -153,11 +155,11 @@ export function readDocument(
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then(async (response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
return errorHandling(response, "reading document", params);
|
return await errorHandling(response, "reading document", params);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +183,7 @@ export function createDocument(
|
|||||||
pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "",
|
pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint();
|
const endpoint = getFeatureEndpointOrDefault("createDocument");
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}/resourcelist?${queryString.stringify(params)}`, {
|
.fetch(`${endpoint}/resourcelist?${queryString.stringify(params)}`, {
|
||||||
@@ -192,11 +194,11 @@ export function createDocument(
|
|||||||
...authHeaders(),
|
...authHeaders(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then(async (response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
return errorHandling(response, "creating document", params);
|
return await errorHandling(response, "creating document", params);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +227,7 @@ export function updateDocument(
|
|||||||
? documentId.partitionKeyProperty
|
? documentId.partitionKeyProperty
|
||||||
: "",
|
: "",
|
||||||
};
|
};
|
||||||
const endpoint = getEndpoint();
|
const endpoint = getFeatureEndpointOrDefault("updateDocument");
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||||
@@ -238,11 +240,11 @@ export function updateDocument(
|
|||||||
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
|
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then(async (response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
return errorHandling(response, "updating document", params);
|
return await errorHandling(response, "updating document", params);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +268,7 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
|
|||||||
? documentId.partitionKeyProperty
|
? documentId.partitionKeyProperty
|
||||||
: "",
|
: "",
|
||||||
};
|
};
|
||||||
const endpoint = getEndpoint();
|
const endpoint = getFeatureEndpointOrDefault("deleteDocument");
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||||
@@ -278,11 +280,11 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
|
|||||||
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
|
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then(async (response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return errorHandling(response, "deleting document", params);
|
return await errorHandling(response, "deleting document", params);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +311,7 @@ export function createMongoCollectionWithProxy(
|
|||||||
autoPilotThroughput: params.autoPilotMaxThroughput?.toString(),
|
autoPilotThroughput: params.autoPilotMaxThroughput?.toString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint();
|
const endpoint = getFeatureEndpointOrDefault("createCollectionWithProxy");
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(
|
.fetch(
|
||||||
@@ -325,16 +327,23 @@ export function createMongoCollectionWithProxy(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then((response) => {
|
.then(async (response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
return errorHandling(response, "creating collection", mongoParams);
|
return await errorHandling(response, "creating collection", mongoParams);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEndpoint(): string {
|
export function getFeatureEndpointOrDefault(feature: string): string {
|
||||||
let url = (configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT) + "/api/mongo/explorer";
|
return hasFlag(userContext.features.mongoProxyAPIs, feature)
|
||||||
|
? getEndpoint(userContext.features.mongoProxyEndpoint)
|
||||||
|
: getEndpoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEndpoint(customEndpoint?: string): string {
|
||||||
|
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");
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import Explorer from "../Explorer/Explorer";
|
import Explorer from "../Explorer/Explorer";
|
||||||
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
|
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
|
import { useDatabases } from "../Explorer/useDatabases";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||||
import * as QueryUtils from "../Utils/QueryUtils";
|
|
||||||
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
||||||
import { createCollection } from "./dataAccess/createCollection";
|
import { createCollection } from "./dataAccess/createCollection";
|
||||||
import { createDocument } from "./dataAccess/createDocument";
|
import { createDocument } from "./dataAccess/createDocument";
|
||||||
import { deleteDocument } from "./dataAccess/deleteDocument";
|
import { deleteDocument } from "./dataAccess/deleteDocument";
|
||||||
import { queryDocuments } from "./dataAccess/queryDocuments";
|
import { queryDocuments } from "./dataAccess/queryDocuments";
|
||||||
import { queryDocumentsPage } from "./dataAccess/queryDocumentsPage";
|
|
||||||
import { handleError } from "./ErrorHandlingUtils";
|
import { handleError } from "./ErrorHandlingUtils";
|
||||||
|
|
||||||
export class QueriesClient {
|
export class QueriesClient {
|
||||||
@@ -100,18 +98,14 @@ export class QueriesClient {
|
|||||||
|
|
||||||
const options: any = { enableCrossPartitionQuery: true };
|
const options: any = { enableCrossPartitionQuery: true };
|
||||||
const clearMessage = NotificationConsoleUtils.logConsoleProgress("Fetching saved queries");
|
const clearMessage = NotificationConsoleUtils.logConsoleProgress("Fetching saved queries");
|
||||||
const queryIterator: QueryIterator<ItemDefinition & Resource> = queryDocuments(
|
const results = await queryDocuments(
|
||||||
SavedQueries.DatabaseName,
|
SavedQueries.DatabaseName,
|
||||||
SavedQueries.CollectionName,
|
SavedQueries.CollectionName,
|
||||||
this.fetchQueriesQuery(),
|
this.fetchQueriesQuery(),
|
||||||
options
|
options
|
||||||
);
|
).fetchAll();
|
||||||
const fetchQueries = async (firstItemIndex: number): Promise<ViewModels.QueryResults> =>
|
|
||||||
await queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex);
|
let queries: DataModels.Query[] = _.map(results.resources, (document: DataModels.Query) => {
|
||||||
return QueryUtils.queryAllPages(fetchQueries)
|
|
||||||
.then(
|
|
||||||
(results: ViewModels.QueryResults) => {
|
|
||||||
let queries: DataModels.Query[] = _.map(results.documents, (document: DataModels.Query) => {
|
|
||||||
if (!document) {
|
if (!document) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -131,14 +125,8 @@ export class QueriesClient {
|
|||||||
});
|
});
|
||||||
queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery);
|
queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery);
|
||||||
NotificationConsoleUtils.logConsoleInfo("Successfully fetched saved queries");
|
NotificationConsoleUtils.logConsoleInfo("Successfully fetched saved queries");
|
||||||
return Promise.resolve(queries);
|
clearMessage();
|
||||||
},
|
return queries;
|
||||||
(error: any) => {
|
|
||||||
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => clearMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteQuery(query: DataModels.Query): Promise<void> {
|
public async deleteQuery(query: DataModels.Query): Promise<void> {
|
||||||
@@ -189,7 +177,7 @@ export class QueriesClient {
|
|||||||
|
|
||||||
private findQueriesCollection(): ViewModels.Collection {
|
private findQueriesCollection(): ViewModels.Collection {
|
||||||
const queriesDatabase: ViewModels.Database = _.find(
|
const queriesDatabase: ViewModels.Database = _.find(
|
||||||
this.container.databases(),
|
useDatabases.getState().databases,
|
||||||
(database: ViewModels.Database) => database.id() === SavedQueries.DatabaseName
|
(database: ViewModels.Database) => database.id() === SavedQueries.DatabaseName
|
||||||
);
|
);
|
||||||
if (!queriesDatabase) {
|
if (!queriesDatabase) {
|
||||||
|
|||||||
85
src/Common/ResourceTreeContainer.tsx
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
|
||||||
|
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
|
||||||
|
import refreshImg from "../../images/refresh-cosmos.svg";
|
||||||
|
import { AuthType } from "../AuthType";
|
||||||
|
import Explorer from "../Explorer/Explorer";
|
||||||
|
import { ResourceTokenTree } from "../Explorer/Tree/ResourceTokenTree";
|
||||||
|
import { ResourceTree } from "../Explorer/Tree/ResourceTree";
|
||||||
|
import { userContext } from "../UserContext";
|
||||||
|
import { NormalizedEventKey } from "./Constants";
|
||||||
|
|
||||||
|
export interface ResourceTreeContainerProps {
|
||||||
|
toggleLeftPaneExpanded: () => void;
|
||||||
|
isLeftPaneExpanded: boolean;
|
||||||
|
container: Explorer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps> = ({
|
||||||
|
toggleLeftPaneExpanded,
|
||||||
|
isLeftPaneExpanded,
|
||||||
|
container,
|
||||||
|
}: ResourceTreeContainerProps): JSX.Element => {
|
||||||
|
const focusButton = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isLeftPaneExpanded) {
|
||||||
|
if (focusButton.current) {
|
||||||
|
focusButton.current.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const onKeyPressToggleLeftPaneExpanded = (event: React.KeyboardEvent) => {
|
||||||
|
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
|
||||||
|
toggleLeftPaneExpanded();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div id="main" className={isLeftPaneExpanded ? "main" : "hiddenMain"}>
|
||||||
|
{/* Collections Window - - Start */}
|
||||||
|
<div id="mainslide" className="flexContainer">
|
||||||
|
{/* Collections Window Title/Command Bar - Start */}
|
||||||
|
<div className="collectiontitle">
|
||||||
|
<div className="coltitle">
|
||||||
|
<span className="titlepadcol">{userContext.apiType} API</span>
|
||||||
|
<div className="float-right">
|
||||||
|
<span
|
||||||
|
className="padimgcolrefresh"
|
||||||
|
data-test="refreshTree"
|
||||||
|
role="button"
|
||||||
|
data-bind="click: onRefreshResourcesClick, clickBubble: false, event: { keypress: onRefreshDatabasesKeyPress }"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label="Refresh tree"
|
||||||
|
title="Refresh tree"
|
||||||
|
>
|
||||||
|
<img className="refreshcol" src={refreshImg} alt="Refresh Tree" />
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="padimgcolrefresh1"
|
||||||
|
id="expandToggleLeftPaneButton"
|
||||||
|
role="button"
|
||||||
|
onClick={toggleLeftPaneExpanded}
|
||||||
|
onKeyPress={onKeyPressToggleLeftPaneExpanded}
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label="Collapse Tree"
|
||||||
|
title="Collapse Tree"
|
||||||
|
ref={focusButton}
|
||||||
|
>
|
||||||
|
<img className="refreshcol1" src={arrowLeftImg} alt="Hide" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{userContext.authType === AuthType.ResourceToken ? (
|
||||||
|
<ResourceTokenTree />
|
||||||
|
) : userContext.features.enableKoResourceTree ? (
|
||||||
|
<div style={{ overflowY: "auto" }} data-bind="react:resourceTree" />
|
||||||
|
) : (
|
||||||
|
<ResourceTree container={container} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{/* Collections Window - End */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
|
|
||||||
import { SplitterMetrics } from "./Constants";
|
|
||||||
|
|
||||||
export enum SplitterDirection {
|
export enum SplitterDirection {
|
||||||
Horizontal = "horizontal",
|
Horizontal = "horizontal",
|
||||||
Vertical = "vertical",
|
Vertical = "vertical",
|
||||||
@@ -28,14 +24,12 @@ export class Splitter {
|
|||||||
public lastX!: number;
|
public lastX!: number;
|
||||||
public lastWidth!: number;
|
public lastWidth!: number;
|
||||||
|
|
||||||
private isCollapsed: ko.Observable<boolean>;
|
|
||||||
private bounds: SplitterBounds;
|
private bounds: SplitterBounds;
|
||||||
private direction: SplitterDirection;
|
private direction: SplitterDirection;
|
||||||
|
|
||||||
constructor(options: SplitterOptions) {
|
constructor(options: SplitterOptions) {
|
||||||
this.splitterId = options.splitterId;
|
this.splitterId = options.splitterId;
|
||||||
this.leftSideId = options.leftId;
|
this.leftSideId = options.leftId;
|
||||||
this.isCollapsed = ko.observable<boolean>(false);
|
|
||||||
this.bounds = options.bounds;
|
this.bounds = options.bounds;
|
||||||
this.direction = options.direction;
|
this.direction = options.direction;
|
||||||
this.initialize();
|
this.initialize();
|
||||||
@@ -83,23 +77,4 @@ export class Splitter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private onResizeStop: JQueryUI.ResizableEvent = () => $("iframe").css("pointer-events", "auto");
|
private onResizeStop: JQueryUI.ResizableEvent = () => $("iframe").css("pointer-events", "auto");
|
||||||
|
|
||||||
public collapseLeft() {
|
|
||||||
this.lastX = $(this.splitter).position().left;
|
|
||||||
this.lastWidth = $(this.leftSide).width();
|
|
||||||
$(this.splitter).css("left", SplitterMetrics.CollapsedPositionLeft);
|
|
||||||
$(this.leftSide).css("width", "");
|
|
||||||
$(this.leftSide).resizable("option", "disabled", true).removeClass("ui-resizable-disabled"); // remove class so splitter is visible
|
|
||||||
$(this.splitter).removeClass("ui-resizable-e");
|
|
||||||
this.isCollapsed(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public expandLeft() {
|
|
||||||
$(this.splitter).addClass("ui-resizable-e");
|
|
||||||
$(this.leftSide).css("width", this.lastWidth);
|
|
||||||
$(this.splitter).css("left", this.lastX);
|
|
||||||
$(this.splitter).css("left", ""); // this ensures the splitter's position is not fixed and enables movement during resizing
|
|
||||||
$(this.leftSide).resizable("enable");
|
|
||||||
this.isCollapsed(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children }:
|
|||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
<TooltipHost content={children}>
|
<TooltipHost content={children}>
|
||||||
<Icon iconName="Info" ariaLabel="Info" className="panelInfoIcon" />
|
<Icon iconName="Info" ariaLabel="Info" className="panelInfoIcon" tabIndex={0} />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
jest.mock("../../Utils/arm/request");
|
jest.mock("../../Utils/arm/request");
|
||||||
jest.mock("../CosmosClient");
|
jest.mock("../CosmosClient");
|
||||||
|
import ko from "knockout";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels";
|
import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels";
|
||||||
|
import { Database } from "../../Contracts/ViewModels";
|
||||||
|
import { useDatabases } from "../../Explorer/useDatabases";
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
import { armRequest } from "../../Utils/arm/request";
|
import { armRequest } from "../../Utils/arm/request";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
@@ -23,6 +26,15 @@ describe("createCollection", () => {
|
|||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
apiType: "SQL",
|
apiType: "SQL",
|
||||||
});
|
});
|
||||||
|
useDatabases.setState({
|
||||||
|
databases: [
|
||||||
|
{
|
||||||
|
id: ko.observable("testDatabase"),
|
||||||
|
loadCollections: () => undefined,
|
||||||
|
collections: ko.observableArray([]),
|
||||||
|
} as Database,
|
||||||
|
],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call ARM if logged in with AAD", async () => {
|
it("should call ARM if logged in with AAD", async () => {
|
||||||
|
|||||||
@@ -4,20 +4,16 @@ import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/Contai
|
|||||||
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
|
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 { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import {
|
import { getCollectionName } from "../../Utils/APITypeUtils";
|
||||||
createUpdateCassandraTable,
|
import { createUpdateCassandraTable } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
||||||
getCassandraTable,
|
import { createUpdateGremlinGraph } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
||||||
} from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
import { createUpdateMongoDBCollection } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
|
||||||
import { createUpdateGremlinGraph, getGremlinGraph } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
import { createUpdateSqlContainer } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
|
||||||
import {
|
import { createUpdateTable } from "../../Utils/arm/generatedClients/cosmos/tableResources";
|
||||||
createUpdateMongoDBCollection,
|
|
||||||
getMongoDBCollection,
|
|
||||||
} from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
|
|
||||||
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
|
|
||||||
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/cosmos/tableResources";
|
|
||||||
import * as ARMTypes from "../../Utils/arm/generatedClients/cosmos/types";
|
import * as ARMTypes from "../../Utils/arm/generatedClients/cosmos/types";
|
||||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
@@ -59,6 +55,16 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||||
|
if (!params.createNewDatabase) {
|
||||||
|
const isValid = await useDatabases.getState().validateCollectionId(params.databaseId, params.collectionId);
|
||||||
|
if (!isValid) {
|
||||||
|
const collectionName = getCollectionName().toLocaleLowerCase();
|
||||||
|
throw new Error(
|
||||||
|
`Create ${collectionName} failed: ${collectionName} with id ${params.collectionId} already exists`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const { apiType } = userContext;
|
const { apiType } = userContext;
|
||||||
switch (apiType) {
|
switch (apiType) {
|
||||||
case "SQL":
|
case "SQL":
|
||||||
@@ -77,23 +83,6 @@ const createCollectionWithARM = async (params: DataModels.CreateCollectionParams
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createSqlContainer = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
const createSqlContainer = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||||
try {
|
|
||||||
const getResponse = await getSqlContainer(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId,
|
|
||||||
params.collectionId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create container failed: container with id ${params.collectionId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
||||||
const resource: ARMTypes.SqlContainerResource = {
|
const resource: ARMTypes.SqlContainerResource = {
|
||||||
id: params.collectionId,
|
id: params.collectionId,
|
||||||
@@ -131,23 +120,6 @@ const createSqlContainer = async (params: DataModels.CreateCollectionParams): Pr
|
|||||||
|
|
||||||
const createMongoCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
const createMongoCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||||
const mongoWildcardIndexOnAllFields: ARMTypes.MongoIndex[] = [{ key: { keys: ["$**"] } }, { key: { keys: ["_id"] } }];
|
const mongoWildcardIndexOnAllFields: ARMTypes.MongoIndex[] = [{ key: { keys: ["$**"] } }, { key: { keys: ["_id"] } }];
|
||||||
try {
|
|
||||||
const getResponse = await getMongoDBCollection(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId,
|
|
||||||
params.collectionId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create collection failed: collection with id ${params.collectionId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
||||||
const resource: ARMTypes.MongoDBCollectionResource = {
|
const resource: ARMTypes.MongoDBCollectionResource = {
|
||||||
id: params.collectionId,
|
id: params.collectionId,
|
||||||
@@ -189,23 +161,6 @@ const createMongoCollection = async (params: DataModels.CreateCollectionParams):
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createCassandraTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
const createCassandraTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||||
try {
|
|
||||||
const getResponse = await getCassandraTable(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId,
|
|
||||||
params.collectionId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create table failed: table with id ${params.collectionId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
||||||
const resource: ARMTypes.CassandraTableResource = {
|
const resource: ARMTypes.CassandraTableResource = {
|
||||||
id: params.collectionId,
|
id: params.collectionId,
|
||||||
@@ -233,23 +188,6 @@ const createCassandraTable = async (params: DataModels.CreateCollectionParams):
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createGraph = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
const createGraph = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||||
try {
|
|
||||||
const getResponse = await getGremlinGraph(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId,
|
|
||||||
params.collectionId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create graph failed: graph with id ${params.collectionId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
||||||
const resource: ARMTypes.GremlinGraphResource = {
|
const resource: ARMTypes.GremlinGraphResource = {
|
||||||
id: params.collectionId,
|
id: params.collectionId,
|
||||||
@@ -284,22 +222,6 @@ const createGraph = async (params: DataModels.CreateCollectionParams): Promise<D
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
const createTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||||
try {
|
|
||||||
const getResponse = await getTable(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.collectionId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create table failed: table with id ${params.collectionId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
||||||
const resource: ARMTypes.TableResource = {
|
const resource: ARMTypes.TableResource = {
|
||||||
id: params.collectionId,
|
id: params.collectionId,
|
||||||
|
|||||||
@@ -2,20 +2,13 @@ import { DatabaseResponse } from "@azure/cosmos";
|
|||||||
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
|
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 { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import {
|
import { getDatabaseName } from "../../Utils/APITypeUtils";
|
||||||
createUpdateCassandraKeyspace,
|
import { createUpdateCassandraKeyspace } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
||||||
getCassandraKeyspace,
|
import { createUpdateGremlinDatabase } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
||||||
} from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
import { createUpdateMongoDBDatabase } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
|
||||||
import {
|
import { createUpdateSqlDatabase } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
|
||||||
createUpdateGremlinDatabase,
|
|
||||||
getGremlinDatabase,
|
|
||||||
} from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
|
||||||
import {
|
|
||||||
createUpdateMongoDBDatabase,
|
|
||||||
getMongoDBDatabase,
|
|
||||||
} from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
|
|
||||||
import { createUpdateSqlDatabase, getSqlDatabase } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
|
|
||||||
import {
|
import {
|
||||||
CassandraKeyspaceCreateUpdateParameters,
|
CassandraKeyspaceCreateUpdateParameters,
|
||||||
CreateUpdateOptions,
|
CreateUpdateOptions,
|
||||||
@@ -48,6 +41,11 @@ export async function createDatabase(params: DataModels.CreateDatabaseParams): P
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||||
|
if (!useDatabases.getState().validateDatabaseId(params.databaseId)) {
|
||||||
|
const databaseName = getDatabaseName().toLocaleLowerCase();
|
||||||
|
throw new Error(`Create ${databaseName} failed: ${databaseName} with id ${params.databaseId} already exists`);
|
||||||
|
}
|
||||||
|
|
||||||
const { apiType } = userContext;
|
const { apiType } = userContext;
|
||||||
|
|
||||||
switch (apiType) {
|
switch (apiType) {
|
||||||
@@ -65,22 +63,6 @@ async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): P
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createSqlDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
async function createSqlDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||||
try {
|
|
||||||
const getResponse = await getSqlDatabase(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: CreateUpdateOptions = constructRpOptions(params);
|
const options: CreateUpdateOptions = constructRpOptions(params);
|
||||||
const rpPayload: SqlDatabaseCreateUpdateParameters = {
|
const rpPayload: SqlDatabaseCreateUpdateParameters = {
|
||||||
properties: {
|
properties: {
|
||||||
@@ -101,22 +83,6 @@ async function createSqlDatabase(params: DataModels.CreateDatabaseParams): Promi
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createMongoDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
async function createMongoDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||||
try {
|
|
||||||
const getResponse = await getMongoDBDatabase(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: CreateUpdateOptions = constructRpOptions(params);
|
const options: CreateUpdateOptions = constructRpOptions(params);
|
||||||
const rpPayload: MongoDBDatabaseCreateUpdateParameters = {
|
const rpPayload: MongoDBDatabaseCreateUpdateParameters = {
|
||||||
properties: {
|
properties: {
|
||||||
@@ -137,22 +103,6 @@ async function createMongoDatabase(params: DataModels.CreateDatabaseParams): Pro
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createCassandraKeyspace(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
async function createCassandraKeyspace(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||||
try {
|
|
||||||
const getResponse = await getCassandraKeyspace(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: CreateUpdateOptions = constructRpOptions(params);
|
const options: CreateUpdateOptions = constructRpOptions(params);
|
||||||
const rpPayload: CassandraKeyspaceCreateUpdateParameters = {
|
const rpPayload: CassandraKeyspaceCreateUpdateParameters = {
|
||||||
properties: {
|
properties: {
|
||||||
@@ -173,22 +123,6 @@ async function createCassandraKeyspace(params: DataModels.CreateDatabaseParams):
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createGremlineDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
async function createGremlineDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||||
try {
|
|
||||||
const getResponse = await getGremlinDatabase(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: CreateUpdateOptions = constructRpOptions(params);
|
const options: CreateUpdateOptions = constructRpOptions(params);
|
||||||
const rpPayload: GremlinDatabaseCreateUpdateParameters = {
|
const rpPayload: GremlinDatabaseCreateUpdateParameters = {
|
||||||
properties: {
|
properties: {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export interface ConfigContext {
|
|||||||
hostedExplorerURL: string;
|
hostedExplorerURL: string;
|
||||||
armAPIVersion?: string;
|
armAPIVersion?: string;
|
||||||
allowedJunoOrigins: string[];
|
allowedJunoOrigins: string[];
|
||||||
enableSchemaAnalyzer: boolean;
|
msalRedirectURI?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default configuration
|
// Default configuration
|
||||||
@@ -62,7 +62,6 @@ let configContext: Readonly<ConfigContext> = {
|
|||||||
"https://tools-staging.cosmos.azure.com",
|
"https://tools-staging.cosmos.azure.com",
|
||||||
"https://localhost",
|
"https://localhost",
|
||||||
],
|
],
|
||||||
enableSchemaAnalyzer: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function resetConfigContext(): void {
|
export function resetConfigContext(): void {
|
||||||
@@ -119,6 +118,14 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
|
|||||||
const armAPIVersion = params.get("armAPIVersion") || "";
|
const armAPIVersion = params.get("armAPIVersion") || "";
|
||||||
updateConfigContext({ armAPIVersion });
|
updateConfigContext({ armAPIVersion });
|
||||||
}
|
}
|
||||||
|
if (params.has("armEndpoint")) {
|
||||||
|
const ARM_ENDPOINT = params.get("armEndpoint") || "";
|
||||||
|
updateConfigContext({ ARM_ENDPOINT });
|
||||||
|
}
|
||||||
|
if (params.has("aadEndpoint")) {
|
||||||
|
const AAD_ENDPOINT = params.get("aadEndpoint") || "";
|
||||||
|
updateConfigContext({ AAD_ENDPOINT });
|
||||||
|
}
|
||||||
if (params.has("platform")) {
|
if (params.has("platform")) {
|
||||||
const platform = params.get("platform");
|
const platform = params.get("platform");
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ConnectionStatusType } from "../Common/Constants";
|
||||||
|
|
||||||
export interface DatabaseAccount {
|
export interface DatabaseAccount {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -9,6 +11,7 @@ export interface DatabaseAccount {
|
|||||||
|
|
||||||
export interface DatabaseAccountExtendedProperties {
|
export interface DatabaseAccountExtendedProperties {
|
||||||
documentEndpoint?: string;
|
documentEndpoint?: string;
|
||||||
|
disableLocalAuth?: boolean;
|
||||||
tableEndpoint?: string;
|
tableEndpoint?: string;
|
||||||
gremlinEndpoint?: string;
|
gremlinEndpoint?: string;
|
||||||
cassandraEndpoint?: string;
|
cassandraEndpoint?: string;
|
||||||
@@ -22,6 +25,7 @@ export interface DatabaseAccountExtendedProperties {
|
|||||||
enableAnalyticalStorage?: boolean;
|
enableAnalyticalStorage?: boolean;
|
||||||
isVirtualNetworkFilterEnabled?: boolean;
|
isVirtualNetworkFilterEnabled?: boolean;
|
||||||
ipRules?: IpRule[];
|
ipRules?: IpRule[];
|
||||||
|
privateEndpointConnections?: unknown[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatabaseAccountResponseLocation {
|
export interface DatabaseAccountResponseLocation {
|
||||||
@@ -391,16 +395,6 @@ export interface GeospatialConfig {
|
|||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GatewayDatabaseAccount {
|
|
||||||
MediaLink: string;
|
|
||||||
DatabasesLink: string;
|
|
||||||
MaxMediaStorageUsageInMB: number;
|
|
||||||
CurrentMediaStorageUsageInMB: number;
|
|
||||||
EnableMultipleWriteLocations?: boolean;
|
|
||||||
WritableLocations: RegionEndpoint[];
|
|
||||||
ReadableLocations: RegionEndpoint[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RegionEndpoint {
|
export interface RegionEndpoint {
|
||||||
name: string;
|
name: string;
|
||||||
documentAccountEndpoint: string;
|
documentAccountEndpoint: string;
|
||||||
@@ -421,13 +415,6 @@ export interface AccountKeys {
|
|||||||
secondaryReadonlyMasterKey: string;
|
secondaryReadonlyMasterKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AfecFeature {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
properties: { state: string };
|
|
||||||
type: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OperationStatus {
|
export interface OperationStatus {
|
||||||
status: string;
|
status: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -507,92 +494,12 @@ export interface MongoParameters extends RpParameters {
|
|||||||
analyticalStorageTtl?: number;
|
analyticalStorageTtl?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SparkClusterLibrary {
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Library extends SparkClusterLibrary {
|
|
||||||
properties: {
|
|
||||||
kind: "Jar";
|
|
||||||
source: {
|
|
||||||
kind: "HttpsUri";
|
|
||||||
uri: string;
|
|
||||||
libraryFileName: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LibraryFeedResponse {
|
|
||||||
value: Library[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ArmResource {
|
|
||||||
id: string;
|
|
||||||
location: string;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
tags: { [key: string]: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ArcadiaWorkspaceIdentity {
|
|
||||||
type: string;
|
|
||||||
principalId: string;
|
|
||||||
tenantId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ArcadiaWorkspaceProperties {
|
|
||||||
managedResourceGroupName: string;
|
|
||||||
provisioningState: string;
|
|
||||||
sqlAdministratorLogin: string;
|
|
||||||
connectivityEndpoints: {
|
|
||||||
artifacts: string;
|
|
||||||
dev: string;
|
|
||||||
spark: string;
|
|
||||||
sql: string;
|
|
||||||
web: string;
|
|
||||||
};
|
|
||||||
defaultDataLakeStorage: {
|
|
||||||
accountUrl: string;
|
|
||||||
filesystem: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ArcadiaWorkspaceFeedResponse {
|
|
||||||
value: ArcadiaWorkspace[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ArcadiaWorkspace extends ArmResource {
|
|
||||||
identity: ArcadiaWorkspaceIdentity;
|
|
||||||
properties: ArcadiaWorkspaceProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SparkPoolFeedResponse {
|
|
||||||
value: SparkPool[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SparkPoolProperties {
|
|
||||||
creationDate: string;
|
|
||||||
sparkVersion: string;
|
|
||||||
nodeCount: number;
|
|
||||||
nodeSize: string;
|
|
||||||
nodeSizeFamily: string;
|
|
||||||
provisioningState: string;
|
|
||||||
autoScale: {
|
|
||||||
enabled: boolean;
|
|
||||||
minNodeCount: number;
|
|
||||||
maxNodeCount: number;
|
|
||||||
};
|
|
||||||
autoPause: {
|
|
||||||
enabled: boolean;
|
|
||||||
delayInMinutes: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SparkPool extends ArmResource {
|
|
||||||
properties: SparkPoolProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MemoryUsageInfo {
|
export interface MemoryUsageInfo {
|
||||||
freeKB: number;
|
freeKB: number;
|
||||||
totalKB: number;
|
totalKB: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ContainerConnectionInfo {
|
||||||
|
status: ConnectionStatusType;
|
||||||
|
//need to add ram and rom info
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import {
|
|||||||
TriggerDefinition,
|
TriggerDefinition,
|
||||||
UserDefinedFunctionDefinition,
|
UserDefinedFunctionDefinition,
|
||||||
} from "@azure/cosmos";
|
} from "@azure/cosmos";
|
||||||
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
|
|
||||||
import Explorer from "../Explorer/Explorer";
|
import Explorer from "../Explorer/Explorer";
|
||||||
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/ConsoleData";
|
||||||
import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient";
|
import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient";
|
||||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
import ConflictId from "../Explorer/Tree/ConflictId";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
@@ -15,6 +14,7 @@ import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
|||||||
import Trigger from "../Explorer/Tree/Trigger";
|
import Trigger from "../Explorer/Tree/Trigger";
|
||||||
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
|
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
|
||||||
import { SelfServeType } from "../SelfServe/SelfServeUtils";
|
import { SelfServeType } from "../SelfServe/SelfServeUtils";
|
||||||
|
import { CollectionCreationDefaults } from "../UserContext";
|
||||||
import { SqlTriggerResource } from "../Utils/arm/generatedClients/cosmos/types";
|
import { SqlTriggerResource } from "../Utils/arm/generatedClients/cosmos/types";
|
||||||
import * as DataModels from "./DataModels";
|
import * as DataModels from "./DataModels";
|
||||||
import { SubscriptionType } from "./SubscriptionType";
|
import { SubscriptionType } from "./SubscriptionType";
|
||||||
@@ -89,7 +89,6 @@ export interface Database extends TreeNode {
|
|||||||
|
|
||||||
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
|
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
|
||||||
|
|
||||||
selectDatabase(): void;
|
|
||||||
expandDatabase(): Promise<void>;
|
expandDatabase(): Promise<void>;
|
||||||
collapseDatabase(): void;
|
collapseDatabase(): void;
|
||||||
|
|
||||||
@@ -275,8 +274,6 @@ export interface TabOptions {
|
|||||||
tabKind: CollectionTabKind;
|
tabKind: CollectionTabKind;
|
||||||
title: string;
|
title: string;
|
||||||
tabPath: string;
|
tabPath: string;
|
||||||
hashLocation: string;
|
|
||||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]) => void;
|
|
||||||
isTabsContentExpanded?: ko.Observable<boolean>;
|
isTabsContentExpanded?: ko.Observable<boolean>;
|
||||||
onLoadStartKey?: number;
|
onLoadStartKey?: number;
|
||||||
|
|
||||||
@@ -287,6 +284,7 @@ export interface TabOptions {
|
|||||||
rid?: string;
|
rid?: string;
|
||||||
node?: TreeNode;
|
node?: TreeNode;
|
||||||
theme?: string;
|
theme?: string;
|
||||||
|
index?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DocumentsTabOptions extends TabOptions {
|
export interface DocumentsTabOptions extends TabOptions {
|
||||||
@@ -411,25 +409,6 @@ export interface SelfServeFrameInputs {
|
|||||||
flights?: readonly string[];
|
flights?: readonly string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionCreationDefaults {
|
|
||||||
storage: string;
|
|
||||||
throughput: ThroughputDefaults;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ThroughputDefaults {
|
|
||||||
fixed: number;
|
|
||||||
unlimited:
|
|
||||||
| number
|
|
||||||
| {
|
|
||||||
collectionThreshold: number;
|
|
||||||
lessThanOrEqualToThreshold: number;
|
|
||||||
greatThanThreshold: number;
|
|
||||||
};
|
|
||||||
unlimitedmax: number;
|
|
||||||
unlimitedmin: number;
|
|
||||||
shared: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MonacoEditorSettings {
|
export class MonacoEditorSettings {
|
||||||
public readonly language: string;
|
public readonly language: string;
|
||||||
public readonly readOnly: boolean;
|
public readonly readOnly: boolean;
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ describe("The Heatmap Control", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let heatmap: Heatmap;
|
let heatmap: Heatmap;
|
||||||
let theme: PortalTheme = 1;
|
const theme: PortalTheme = 1;
|
||||||
const divElement: string = `<div id="${Heatmap.elementId}"></div>`;
|
const divElement = `<div id="${Heatmap.elementId}"></div>`;
|
||||||
|
|
||||||
describe("drawHeatmap rendering", () => {
|
describe("drawHeatmap rendering", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -100,7 +100,7 @@ describe("iframe rendering when there is no data", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should show a no data message with a dark theme", () => {
|
it("should show a no data message with a dark theme", () => {
|
||||||
let data = {
|
const data = {
|
||||||
data: {
|
data: {
|
||||||
signature: "pcIframe",
|
signature: "pcIframe",
|
||||||
data: {
|
data: {
|
||||||
@@ -111,7 +111,7 @@ describe("iframe rendering when there is no data", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const divElement: string = `<div id="${Heatmap.elementId}"></div>`;
|
const divElement = `<div id="${Heatmap.elementId}"></div>`;
|
||||||
document.body.innerHTML = divElement;
|
document.body.innerHTML = divElement;
|
||||||
|
|
||||||
handleMessage(data as MessageEvent);
|
handleMessage(data as MessageEvent);
|
||||||
@@ -120,7 +120,7 @@ describe("iframe rendering when there is no data", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should show a no data message with a white theme", () => {
|
it("should show a no data message with a white theme", () => {
|
||||||
let data = {
|
const data = {
|
||||||
data: {
|
data: {
|
||||||
signature: "pcIframe",
|
signature: "pcIframe",
|
||||||
data: {
|
data: {
|
||||||
@@ -131,7 +131,7 @@ describe("iframe rendering when there is no data", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const divElement: string = `<div id="${Heatmap.elementId}"></div>`;
|
const divElement = `<div id="${Heatmap.elementId}"></div>`;
|
||||||
document.body.innerHTML = divElement;
|
document.body.innerHTML = divElement;
|
||||||
|
|
||||||
handleMessage(data as MessageEvent);
|
handleMessage(data as MessageEvent);
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export class Heatmap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getFontStyles(size: number = StyleConstants.MediumFontSize, color: string = "#838383"): FontSettings {
|
private _getFontStyles(size: number = StyleConstants.MediumFontSize, color = "#838383"): FontSettings {
|
||||||
return {
|
return {
|
||||||
family: StyleConstants.DataExplorerFont,
|
family: StyleConstants.DataExplorerFont,
|
||||||
size,
|
size,
|
||||||
@@ -78,9 +78,9 @@ export class Heatmap {
|
|||||||
// go thru all rows and create 2d matrix for heatmap...
|
// go thru all rows and create 2d matrix for heatmap...
|
||||||
for (let i = 0; i < rows.length; i++) {
|
for (let i = 0; i < rows.length; i++) {
|
||||||
output.yAxisPoints.push(rows[i]);
|
output.yAxisPoints.push(rows[i]);
|
||||||
let dataPoints: number[] = [];
|
const dataPoints: number[] = [];
|
||||||
for (let a = 0; a < output.xAxisPoints.length; a++) {
|
for (let a = 0; a < output.xAxisPoints.length; a++) {
|
||||||
let row: PartitionTimeStampToData = data[rows[i]];
|
const row: PartitionTimeStampToData = data[rows[i]];
|
||||||
dataPoints.push(row[output.xAxisPoints[a]]["Normalized Throughput"]);
|
dataPoints.push(row[output.xAxisPoints[a]]["Normalized Throughput"]);
|
||||||
}
|
}
|
||||||
output.dataPoints.push(dataPoints);
|
output.dataPoints.push(dataPoints);
|
||||||
@@ -193,7 +193,7 @@ export class Heatmap {
|
|||||||
this._getLayoutSettings(),
|
this._getLayoutSettings(),
|
||||||
this._getChartDisplaySettings()
|
this._getChartDisplaySettings()
|
||||||
);
|
);
|
||||||
let plotDiv: any = document.getElementById(Heatmap.elementId);
|
const plotDiv: any = document.getElementById(Heatmap.elementId);
|
||||||
plotDiv.on("plotly_click", (data: any) => {
|
plotDiv.on("plotly_click", (data: any) => {
|
||||||
let timeSelected: string = data.points[0].x;
|
let timeSelected: string = data.points[0].x;
|
||||||
timeSelected = timeSelected.replace(" ", "T");
|
timeSelected = timeSelected.replace(" ", "T");
|
||||||
@@ -205,7 +205,7 @@ export class Heatmap {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let output = [];
|
const output = [];
|
||||||
for (let i = 0; i < this._chartData.dataPoints.length; i++) {
|
for (let i = 0; i < this._chartData.dataPoints.length; i++) {
|
||||||
output.push(this._chartData.dataPoints[i][xAxisIndex]);
|
output.push(this._chartData.dataPoints[i][xAxisIndex]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
jest.mock("monaco-editor");
|
|
||||||
|
|
||||||
import * as ko from "knockout";
|
|
||||||
import "./ComponentRegisterer";
|
|
||||||
|
|
||||||
describe("Component Registerer", () => {
|
|
||||||
it("should register json-editor component", () => {
|
|
||||||
expect(ko.components.isRegistered("json-editor")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should register dynamic-list component", () => {
|
|
||||||
expect(ko.components.isRegistered("dynamic-list")).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,12 +1,8 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent";
|
import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent";
|
||||||
import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent";
|
|
||||||
import { EditorComponent } from "./Controls/Editor/EditorComponent";
|
import { EditorComponent } from "./Controls/Editor/EditorComponent";
|
||||||
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
|
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
|
||||||
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
|
|
||||||
|
|
||||||
ko.components.register("editor", new EditorComponent());
|
ko.components.register("editor", new EditorComponent());
|
||||||
ko.components.register("json-editor", new JsonEditorComponent());
|
ko.components.register("json-editor", new JsonEditorComponent());
|
||||||
ko.components.register("diff-editor", new DiffEditorComponent());
|
ko.components.register("diff-editor", new DiffEditorComponent());
|
||||||
ko.components.register("dynamic-list", DynamicListComponent);
|
|
||||||
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
|
||||||
|
|||||||
@@ -1,172 +0,0 @@
|
|||||||
import AddCollectionIcon from "../../images/AddCollection.svg";
|
|
||||||
import AddSqlQueryIcon from "../../images/AddSqlQuery_16x16.svg";
|
|
||||||
import AddStoredProcedureIcon from "../../images/AddStoredProcedure.svg";
|
|
||||||
import AddTriggerIcon from "../../images/AddTrigger.svg";
|
|
||||||
import AddUdfIcon from "../../images/AddUdf.svg";
|
|
||||||
import DeleteCollectionIcon from "../../images/DeleteCollection.svg";
|
|
||||||
import DeleteDatabaseIcon from "../../images/DeleteDatabase.svg";
|
|
||||||
import DeleteSprocIcon from "../../images/DeleteSproc.svg";
|
|
||||||
import DeleteTriggerIcon from "../../images/DeleteTrigger.svg";
|
|
||||||
import DeleteUDFIcon from "../../images/DeleteUDF.svg";
|
|
||||||
import HostedTerminalIcon from "../../images/Hosted-Terminal.svg";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
|
||||||
import { userContext } from "../UserContext";
|
|
||||||
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
|
|
||||||
import Explorer from "./Explorer";
|
|
||||||
import StoredProcedure from "./Tree/StoredProcedure";
|
|
||||||
import Trigger from "./Tree/Trigger";
|
|
||||||
import UserDefinedFunction from "./Tree/UserDefinedFunction";
|
|
||||||
|
|
||||||
export interface CollectionContextMenuButtonParams {
|
|
||||||
databaseId: string;
|
|
||||||
collectionId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DatabaseContextMenuButtonParams {
|
|
||||||
databaseId: string;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* New resource tree (in ReactJS)
|
|
||||||
*/
|
|
||||||
export class ResourceTreeContextMenuButtonFactory {
|
|
||||||
public static createDatabaseContextMenu(container: Explorer, databaseId: string): TreeNodeMenuItem[] {
|
|
||||||
const items: TreeNodeMenuItem[] = [
|
|
||||||
{
|
|
||||||
iconSrc: AddCollectionIcon,
|
|
||||||
onClick: () => container.onNewCollectionClicked(databaseId),
|
|
||||||
label: container.addCollectionText(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (userContext.apiType !== "Tables") {
|
|
||||||
items.push({
|
|
||||||
iconSrc: DeleteDatabaseIcon,
|
|
||||||
onClick: () => container.openDeleteDatabaseConfirmationPane(),
|
|
||||||
label: container.deleteDatabaseText(),
|
|
||||||
styleClass: "deleteDatabaseMenuItem",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static createCollectionContextMenuButton(
|
|
||||||
container: Explorer,
|
|
||||||
selectedCollection: ViewModels.Collection
|
|
||||||
): TreeNodeMenuItem[] {
|
|
||||||
const items: TreeNodeMenuItem[] = [];
|
|
||||||
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
|
|
||||||
items.push({
|
|
||||||
iconSrc: AddSqlQueryIcon,
|
|
||||||
onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null),
|
|
||||||
label: "New SQL Query",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userContext.apiType === "Mongo") {
|
|
||||||
items.push({
|
|
||||||
iconSrc: AddSqlQueryIcon,
|
|
||||||
onClick: () => selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null),
|
|
||||||
label: "New Query",
|
|
||||||
});
|
|
||||||
|
|
||||||
items.push({
|
|
||||||
iconSrc: HostedTerminalIcon,
|
|
||||||
onClick: () => {
|
|
||||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
|
||||||
if (container.isShellEnabled()) {
|
|
||||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
|
||||||
} else {
|
|
||||||
selectedCollection && selectedCollection.onNewMongoShellClick();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
label: container.isShellEnabled() ? "Open Mongo Shell" : "New Shell",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
|
|
||||||
items.push({
|
|
||||||
iconSrc: AddStoredProcedureIcon,
|
|
||||||
onClick: () => {
|
|
||||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
|
||||||
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null);
|
|
||||||
},
|
|
||||||
label: "New Stored Procedure",
|
|
||||||
});
|
|
||||||
|
|
||||||
items.push({
|
|
||||||
iconSrc: AddUdfIcon,
|
|
||||||
onClick: () => {
|
|
||||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
|
||||||
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, null);
|
|
||||||
},
|
|
||||||
label: "New UDF",
|
|
||||||
});
|
|
||||||
|
|
||||||
items.push({
|
|
||||||
iconSrc: AddTriggerIcon,
|
|
||||||
onClick: () => {
|
|
||||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
|
||||||
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, null);
|
|
||||||
},
|
|
||||||
label: "New Trigger",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
items.push({
|
|
||||||
iconSrc: DeleteCollectionIcon,
|
|
||||||
onClick: () => container.openDeleteCollectionConfirmationPane(),
|
|
||||||
label: container.deleteCollectionText(),
|
|
||||||
styleClass: "deleteCollectionMenuItem",
|
|
||||||
});
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static createStoreProcedureContextMenuItems(
|
|
||||||
container: Explorer,
|
|
||||||
storedProcedure: StoredProcedure
|
|
||||||
): TreeNodeMenuItem[] {
|
|
||||||
if (userContext.apiType === "Cassandra") {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
iconSrc: DeleteSprocIcon,
|
|
||||||
onClick: () => storedProcedure.delete(),
|
|
||||||
label: "Delete Store Procedure",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static createTriggerContextMenuItems(container: Explorer, trigger: Trigger): TreeNodeMenuItem[] {
|
|
||||||
if (userContext.apiType === "Cassandra") {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
iconSrc: DeleteTriggerIcon,
|
|
||||||
onClick: () => trigger.delete(),
|
|
||||||
label: "Delete Trigger",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static createUserDefinedFunctionContextMenuItems(
|
|
||||||
container: Explorer,
|
|
||||||
userDefinedFunction: UserDefinedFunction
|
|
||||||
): TreeNodeMenuItem[] {
|
|
||||||
if (userContext.apiType === "Cassandra") {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
iconSrc: DeleteUDFIcon,
|
|
||||||
onClick: () => userDefinedFunction.delete(),
|
|
||||||
label: "Delete User Defined Function",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
190
src/Explorer/ContextMenuButtonFactory.tsx
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
import React from "react";
|
||||||
|
import AddCollectionIcon from "../../images/AddCollection.svg";
|
||||||
|
import AddSqlQueryIcon from "../../images/AddSqlQuery_16x16.svg";
|
||||||
|
import AddStoredProcedureIcon from "../../images/AddStoredProcedure.svg";
|
||||||
|
import AddTriggerIcon from "../../images/AddTrigger.svg";
|
||||||
|
import AddUdfIcon from "../../images/AddUdf.svg";
|
||||||
|
import DeleteCollectionIcon from "../../images/DeleteCollection.svg";
|
||||||
|
import DeleteDatabaseIcon from "../../images/DeleteDatabase.svg";
|
||||||
|
import DeleteSprocIcon from "../../images/DeleteSproc.svg";
|
||||||
|
import DeleteTriggerIcon from "../../images/DeleteTrigger.svg";
|
||||||
|
import DeleteUDFIcon from "../../images/DeleteUDF.svg";
|
||||||
|
import HostedTerminalIcon from "../../images/Hosted-Terminal.svg";
|
||||||
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
|
import { useSidePanel } from "../hooks/useSidePanel";
|
||||||
|
import { userContext } from "../UserContext";
|
||||||
|
import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils";
|
||||||
|
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
|
||||||
|
import Explorer from "./Explorer";
|
||||||
|
import { useNotebook } from "./Notebook/useNotebook";
|
||||||
|
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
|
||||||
|
import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel";
|
||||||
|
import StoredProcedure from "./Tree/StoredProcedure";
|
||||||
|
import Trigger from "./Tree/Trigger";
|
||||||
|
import UserDefinedFunction from "./Tree/UserDefinedFunction";
|
||||||
|
import { useSelectedNode } from "./useSelectedNode";
|
||||||
|
|
||||||
|
export interface CollectionContextMenuButtonParams {
|
||||||
|
databaseId: string;
|
||||||
|
collectionId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DatabaseContextMenuButtonParams {
|
||||||
|
databaseId: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* New resource tree (in ReactJS)
|
||||||
|
*/
|
||||||
|
export const createDatabaseContextMenu = (container: Explorer, databaseId: string): TreeNodeMenuItem[] => {
|
||||||
|
const items: TreeNodeMenuItem[] = [
|
||||||
|
{
|
||||||
|
iconSrc: AddCollectionIcon,
|
||||||
|
onClick: () => container.onNewCollectionClicked(databaseId),
|
||||||
|
label: `New ${getCollectionName()}`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (userContext.apiType !== "Tables") {
|
||||||
|
items.push({
|
||||||
|
iconSrc: DeleteDatabaseIcon,
|
||||||
|
onClick: () =>
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel(
|
||||||
|
"Delete " + getDatabaseName(),
|
||||||
|
<DeleteDatabaseConfirmationPanel refreshDatabases={() => container.refreshAllDatabases()} />
|
||||||
|
),
|
||||||
|
label: `Delete ${getDatabaseName()}`,
|
||||||
|
styleClass: "deleteDatabaseMenuItem",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createCollectionContextMenuButton = (
|
||||||
|
container: Explorer,
|
||||||
|
selectedCollection: ViewModels.Collection
|
||||||
|
): TreeNodeMenuItem[] => {
|
||||||
|
const items: TreeNodeMenuItem[] = [];
|
||||||
|
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
|
||||||
|
items.push({
|
||||||
|
iconSrc: AddSqlQueryIcon,
|
||||||
|
onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, undefined),
|
||||||
|
label: "New SQL Query",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userContext.apiType === "Mongo") {
|
||||||
|
items.push({
|
||||||
|
iconSrc: AddSqlQueryIcon,
|
||||||
|
onClick: () => selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, undefined),
|
||||||
|
label: "New Query",
|
||||||
|
});
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
iconSrc: HostedTerminalIcon,
|
||||||
|
isDisabled: useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown,
|
||||||
|
onClick: () => {
|
||||||
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
|
if (useNotebook.getState().isShellEnabled) {
|
||||||
|
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||||
|
} else {
|
||||||
|
selectedCollection && selectedCollection.onNewMongoShellClick();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label: useNotebook.getState().isShellEnabled ? "Open Mongo Shell" : "New Shell",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
|
||||||
|
items.push({
|
||||||
|
iconSrc: AddStoredProcedureIcon,
|
||||||
|
onClick: () => {
|
||||||
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
|
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, undefined);
|
||||||
|
},
|
||||||
|
label: "New Stored Procedure",
|
||||||
|
});
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
iconSrc: AddUdfIcon,
|
||||||
|
onClick: () => {
|
||||||
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
|
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection);
|
||||||
|
},
|
||||||
|
label: "New UDF",
|
||||||
|
});
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
iconSrc: AddTriggerIcon,
|
||||||
|
onClick: () => {
|
||||||
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
|
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, undefined);
|
||||||
|
},
|
||||||
|
label: "New Trigger",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
iconSrc: DeleteCollectionIcon,
|
||||||
|
onClick: () =>
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel(
|
||||||
|
"Delete " + getCollectionName(),
|
||||||
|
<DeleteCollectionConfirmationPane refreshDatabases={() => container.refreshAllDatabases()} />
|
||||||
|
),
|
||||||
|
label: `Delete ${getCollectionName()}`,
|
||||||
|
styleClass: "deleteCollectionMenuItem",
|
||||||
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createStoreProcedureContextMenuItems = (
|
||||||
|
container: Explorer,
|
||||||
|
storedProcedure: StoredProcedure
|
||||||
|
): TreeNodeMenuItem[] => {
|
||||||
|
if (userContext.apiType === "Cassandra") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
iconSrc: DeleteSprocIcon,
|
||||||
|
onClick: () => storedProcedure.delete(),
|
||||||
|
label: "Delete Store Procedure",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createTriggerContextMenuItems = (container: Explorer, trigger: Trigger): TreeNodeMenuItem[] => {
|
||||||
|
if (userContext.apiType === "Cassandra") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
iconSrc: DeleteTriggerIcon,
|
||||||
|
onClick: () => trigger.delete(),
|
||||||
|
label: "Delete Trigger",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createUserDefinedFunctionContextMenuItems = (
|
||||||
|
container: Explorer,
|
||||||
|
userDefinedFunction: UserDefinedFunction
|
||||||
|
): TreeNodeMenuItem[] => {
|
||||||
|
if (userContext.apiType === "Cassandra") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
iconSrc: DeleteUDFIcon,
|
||||||
|
onClick: () => userDefinedFunction.delete(),
|
||||||
|
label: "Delete User Defined Function",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
@@ -8,7 +8,9 @@ import TriangleDownIcon from "../../../../images/Triangle-down.svg";
|
|||||||
import TriangleRightIcon from "../../../../images/Triangle-right.svg";
|
import TriangleRightIcon from "../../../../images/Triangle-right.svg";
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
|
|
||||||
export interface AccordionComponentProps {}
|
export interface AccordionComponentProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
export class AccordionComponent extends React.Component<AccordionComponentProps> {
|
export class AccordionComponent extends React.Component<AccordionComponentProps> {
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
@@ -78,7 +80,7 @@ export class AccordionItemComponent extends React.Component<AccordionItemCompone
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onHeaderClick = (_event: React.MouseEvent<HTMLDivElement>): void => {
|
private onHeaderClick = (): void => {
|
||||||
this.setState({ isExpanded: !this.state.isExpanded });
|
this.setState({ isExpanded: !this.state.isExpanded });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
import { DefaultButton, IButtonStyles, IContextualMenuItem, IContextualMenuProps } from "@fluentui/react";
|
|
||||||
import * as React from "react";
|
|
||||||
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
|
|
||||||
import * as Logger from "../../../Common/Logger";
|
|
||||||
import { ArcadiaWorkspace, SparkPool } from "../../../Contracts/DataModels";
|
|
||||||
|
|
||||||
export interface ArcadiaMenuPickerProps {
|
|
||||||
selectText?: string;
|
|
||||||
disableSubmenu?: boolean;
|
|
||||||
selectedSparkPool: string;
|
|
||||||
workspaces: ArcadiaWorkspaceItem[];
|
|
||||||
onSparkPoolSelect: (
|
|
||||||
e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
|
|
||||||
item: IContextualMenuItem
|
|
||||||
) => boolean | void;
|
|
||||||
onCreateNewWorkspaceClicked: () => boolean | void;
|
|
||||||
onCreateNewSparkPoolClicked: (workspaceResourceId: string) => boolean | void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ArcadiaMenuPickerStates {
|
|
||||||
selectedSparkPool: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ArcadiaWorkspaceItem extends ArcadiaWorkspace {
|
|
||||||
sparkPools: SparkPool[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, ArcadiaMenuPickerStates> {
|
|
||||||
constructor(props: ArcadiaMenuPickerProps) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
selectedSparkPool: props.selectedSparkPool,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private _onSparkPoolClicked = (
|
|
||||||
e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
|
|
||||||
item: IContextualMenuItem
|
|
||||||
): boolean | void => {
|
|
||||||
try {
|
|
||||||
this.props.onSparkPoolSelect(e, item);
|
|
||||||
this.setState({
|
|
||||||
selectedSparkPool: item.text,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
Logger.logError(getErrorMessage(error), "ArcadiaMenuPicker/_onSparkPoolClicked");
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private _onCreateNewWorkspaceClicked = (
|
|
||||||
e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
|
|
||||||
item: IContextualMenuItem
|
|
||||||
): boolean | void => {
|
|
||||||
this.props.onCreateNewWorkspaceClicked();
|
|
||||||
};
|
|
||||||
|
|
||||||
private _onCreateNewSparkPoolClicked = (
|
|
||||||
e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
|
|
||||||
item: IContextualMenuItem
|
|
||||||
): boolean | void => {
|
|
||||||
this.props.onCreateNewSparkPoolClicked(item.key);
|
|
||||||
};
|
|
||||||
|
|
||||||
public render() {
|
|
||||||
const { workspaces } = this.props;
|
|
||||||
let workspaceMenuItems: IContextualMenuItem[] = workspaces.map((workspace) => {
|
|
||||||
let sparkPoolsMenuProps: IContextualMenuProps = {
|
|
||||||
items: workspace.sparkPools.map(
|
|
||||||
(sparkpool): IContextualMenuItem => ({
|
|
||||||
key: sparkpool.id,
|
|
||||||
text: sparkpool.name,
|
|
||||||
onClick: this._onSparkPoolClicked,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
};
|
|
||||||
if (!sparkPoolsMenuProps.items.length) {
|
|
||||||
sparkPoolsMenuProps.items.push({
|
|
||||||
key: workspace.id,
|
|
||||||
text: "Create new spark pool",
|
|
||||||
onClick: this._onCreateNewSparkPoolClicked,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: workspace.id,
|
|
||||||
text: workspace.name,
|
|
||||||
subMenuProps: this.props.disableSubmenu ? undefined : sparkPoolsMenuProps,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!workspaceMenuItems.length) {
|
|
||||||
workspaceMenuItems.push({
|
|
||||||
key: "create_workspace",
|
|
||||||
text: "Create new workspace",
|
|
||||||
onClick: this._onCreateNewWorkspaceClicked,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const dropdownStyle: IButtonStyles = {
|
|
||||||
root: {
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
margin: "auto 5px",
|
|
||||||
padding: "0",
|
|
||||||
border: "0",
|
|
||||||
},
|
|
||||||
rootHovered: {
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
},
|
|
||||||
rootChecked: {
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
},
|
|
||||||
rootFocused: {
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
},
|
|
||||||
rootExpanded: {
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
},
|
|
||||||
flexContainer: {
|
|
||||||
height: "30px",
|
|
||||||
border: "1px solid #a6a6a6",
|
|
||||||
padding: "0 8px",
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
fontWeight: "400",
|
|
||||||
fontSize: "12px",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DefaultButton
|
|
||||||
text={this.state.selectedSparkPool || this.props.selectText || "Select a Spark pool"}
|
|
||||||
persistMenu={true}
|
|
||||||
className="arcadia-menu-picker"
|
|
||||||
menuProps={{
|
|
||||||
items: workspaceMenuItems,
|
|
||||||
}}
|
|
||||||
styles={dropdownStyle}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Icon, Label, Stack } from "@fluentui/react";
|
import { Icon, Label, Stack } from "@fluentui/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { NormalizedEventKey } from "../../../Common/Constants";
|
||||||
import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
|
import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
|
||||||
|
|
||||||
export interface CollapsibleSectionProps {
|
export interface CollapsibleSectionProps {
|
||||||
@@ -30,6 +31,13 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onKeyPress = (event: React.KeyboardEvent) => {
|
||||||
|
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
|
||||||
|
this.toggleCollapsed();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -39,6 +47,11 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
|
|||||||
verticalAlign="center"
|
verticalAlign="center"
|
||||||
tokens={accordionStackTokens}
|
tokens={accordionStackTokens}
|
||||||
onClick={this.toggleCollapsed}
|
onClick={this.toggleCollapsed}
|
||||||
|
onKeyPress={this.onKeyPress}
|
||||||
|
tabIndex={0}
|
||||||
|
aria-name="Advanced"
|
||||||
|
role="button"
|
||||||
|
aria-expanded={this.state.isExpanded}
|
||||||
>
|
>
|
||||||
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} />
|
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} />
|
||||||
<Label>{this.props.title}</Label>
|
<Label>{this.props.title}</Label>
|
||||||
|
|||||||
@@ -3,9 +3,14 @@
|
|||||||
exports[`CollapsibleSectionComponent renders 1`] = `
|
exports[`CollapsibleSectionComponent renders 1`] = `
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Stack
|
<Stack
|
||||||
|
aria-expanded={true}
|
||||||
|
aria-name="Advanced"
|
||||||
className="collapsibleSection"
|
className="collapsibleSection"
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
|
onKeyPress={[Function]}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
tokens={
|
tokens={
|
||||||
Object {
|
Object {
|
||||||
"childrenGap": 10,
|
"childrenGap": 10,
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import * as StringUtils from "../../../Utils/StringUtils";
|
|
||||||
import { KeyCodes } from "../../../Common/Constants";
|
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* React component for Command button component.
|
* React component for Command button component.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ArcadiaMenuPickerProps } from "../Arcadia/ArcadiaMenuPicker";
|
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
||||||
|
import { KeyCodes } from "../../../Common/Constants";
|
||||||
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import * as StringUtils from "../../../Utils/StringUtils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for this component
|
* Options for this component
|
||||||
@@ -114,15 +111,6 @@ export interface CommandButtonComponentProps {
|
|||||||
* Aria-label for the button
|
* Aria-label for the button
|
||||||
*/
|
*/
|
||||||
ariaLabel: string;
|
ariaLabel: string;
|
||||||
//TODO: generalize customized command bar
|
|
||||||
/**
|
|
||||||
* If set to true, will render arcadia picker
|
|
||||||
*/
|
|
||||||
isArcadiaPicker?: boolean;
|
|
||||||
/**
|
|
||||||
* props to render arcadia picker
|
|
||||||
*/
|
|
||||||
arcadiaProps?: ArcadiaMenuPickerProps;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CommandButtonComponent extends React.Component<CommandButtonComponentProps> {
|
export class CommandButtonComponent extends React.Component<CommandButtonComponentProps> {
|
||||||
@@ -133,8 +121,7 @@ export class CommandButtonComponent extends React.Component<CommandButtonCompone
|
|||||||
if (!this.dropdownElt || !this.expandButtonElt) {
|
if (!this.dropdownElt || !this.expandButtonElt) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
$(this.dropdownElt).offset({ left: $(this.expandButtonElt).offset().left });
|
||||||
const dropdownElt = $(this.dropdownElt).offset({ left: $(this.expandButtonElt).offset().left });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private onKeyPress(event: React.KeyboardEvent): boolean {
|
private onKeyPress(event: React.KeyboardEvent): boolean {
|
||||||
|
|||||||
@@ -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";
|
||||||
@@ -23,13 +24,78 @@ export interface DialogState {
|
|||||||
dialogProps?: DialogProps;
|
dialogProps?: DialogProps;
|
||||||
openDialog: (props: DialogProps) => void;
|
openDialog: (props: DialogProps) => void;
|
||||||
closeDialog: () => void;
|
closeDialog: () => void;
|
||||||
|
showOkCancelModalDialog: (
|
||||||
|
title: string,
|
||||||
|
subText: string,
|
||||||
|
okLabel: string,
|
||||||
|
onOk: () => void,
|
||||||
|
cancelLabel: string,
|
||||||
|
onCancel: () => void,
|
||||||
|
contentHtml?: JSX.Element,
|
||||||
|
choiceGroupProps?: IChoiceGroupProps,
|
||||||
|
textFieldProps?: TextFieldProps,
|
||||||
|
primaryButtonDisabled?: boolean
|
||||||
|
) => void;
|
||||||
|
showOkModalDialog: (title: string, subText: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useDialog: UseStore<DialogState> = create((set) => ({
|
export const useDialog: UseStore<DialogState> = create((set, get) => ({
|
||||||
visible: false,
|
visible: false,
|
||||||
openDialog: (props: DialogProps) => set(() => ({ visible: true, dialogProps: props })),
|
openDialog: (props: DialogProps) => set(() => ({ visible: true, dialogProps: props })),
|
||||||
closeDialog: () =>
|
closeDialog: () =>
|
||||||
set((state) => ({ visible: false, openDialog: state.openDialog, closeDialog: state.closeDialog }), true),
|
set(
|
||||||
|
(state) => ({
|
||||||
|
visible: false,
|
||||||
|
openDialog: state.openDialog,
|
||||||
|
closeDialog: state.closeDialog,
|
||||||
|
showOkCancelModalDialog: state.showOkCancelModalDialog,
|
||||||
|
showOkModalDialog: state.showOkModalDialog,
|
||||||
|
}),
|
||||||
|
true // TODO: This probably should not be true but its causing a prod bug so easier to just set the proper state above
|
||||||
|
),
|
||||||
|
showOkCancelModalDialog: (
|
||||||
|
title: string,
|
||||||
|
subText: string,
|
||||||
|
okLabel: string,
|
||||||
|
onOk: () => void,
|
||||||
|
cancelLabel: string,
|
||||||
|
onCancel: () => void,
|
||||||
|
contentHtml?: JSX.Element,
|
||||||
|
choiceGroupProps?: IChoiceGroupProps,
|
||||||
|
textFieldProps?: TextFieldProps,
|
||||||
|
primaryButtonDisabled?: boolean
|
||||||
|
): void =>
|
||||||
|
get().openDialog({
|
||||||
|
isModal: true,
|
||||||
|
title,
|
||||||
|
subText,
|
||||||
|
primaryButtonText: okLabel,
|
||||||
|
secondaryButtonText: cancelLabel,
|
||||||
|
onPrimaryButtonClick: () => {
|
||||||
|
get().closeDialog();
|
||||||
|
onOk && onOk();
|
||||||
|
},
|
||||||
|
onSecondaryButtonClick: () => {
|
||||||
|
get().closeDialog();
|
||||||
|
onCancel && onCancel();
|
||||||
|
},
|
||||||
|
contentHtml,
|
||||||
|
choiceGroupProps,
|
||||||
|
textFieldProps,
|
||||||
|
primaryButtonDisabled,
|
||||||
|
}),
|
||||||
|
showOkModalDialog: (title: string, subText: string): void =>
|
||||||
|
get().openDialog({
|
||||||
|
isModal: true,
|
||||||
|
title,
|
||||||
|
subText,
|
||||||
|
primaryButtonText: "Close",
|
||||||
|
secondaryButtonText: undefined,
|
||||||
|
onPrimaryButtonClick: () => {
|
||||||
|
get().closeDialog();
|
||||||
|
},
|
||||||
|
onSecondaryButtonClick: undefined,
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export interface TextFieldProps extends ITextFieldProps {
|
export interface TextFieldProps extends ITextFieldProps {
|
||||||
@@ -62,6 +128,7 @@ export interface DialogProps {
|
|||||||
type?: DialogType;
|
type?: DialogType;
|
||||||
showCloseButton?: boolean;
|
showCloseButton?: boolean;
|
||||||
onDismiss?: () => void;
|
onDismiss?: () => void;
|
||||||
|
contentHtml?: JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DIALOG_MIN_WIDTH = "400px";
|
const DIALOG_MIN_WIDTH = "400px";
|
||||||
@@ -88,6 +155,7 @@ export const Dialog: FC = () => {
|
|||||||
type,
|
type,
|
||||||
showCloseButton,
|
showCloseButton,
|
||||||
onDismiss,
|
onDismiss,
|
||||||
|
contentHtml,
|
||||||
} = props || {};
|
} = props || {};
|
||||||
|
|
||||||
const dialogProps: IDialogProps = {
|
const dialogProps: IDialogProps = {
|
||||||
@@ -119,8 +187,7 @@ export const Dialog: FC = () => {
|
|||||||
text: secondaryButtonText,
|
text: secondaryButtonText,
|
||||||
onClick: onSecondaryButtonClick,
|
onClick: onSecondaryButtonClick,
|
||||||
}
|
}
|
||||||
: {};
|
: undefined;
|
||||||
|
|
||||||
return visible ? (
|
return visible ? (
|
||||||
<FluentDialog {...dialogProps}>
|
<FluentDialog {...dialogProps}>
|
||||||
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
|
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
|
||||||
@@ -130,6 +197,7 @@ export const Dialog: FC = () => {
|
|||||||
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
|
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
{contentHtml && <Text>{contentHtml}</Text>}
|
||||||
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
|
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<PrimaryButton {...primaryButtonProps} />
|
<PrimaryButton {...primaryButtonProps} />
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { shallow, mount } from "enzyme";
|
|
||||||
import { DefaultDirectoryDropdownComponent, DefaultDirectoryDropdownProps } from "./DefaultDirectoryDropdownComponent";
|
|
||||||
import { Tenant } from "../../../Contracts/DataModels";
|
|
||||||
|
|
||||||
const createBlankProps = (): DefaultDirectoryDropdownProps => {
|
|
||||||
return {
|
|
||||||
defaultDirectoryId: "",
|
|
||||||
directories: [],
|
|
||||||
onDefaultDirectoryChange: jest.fn(),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const createBlankDirectory = (): Tenant => {
|
|
||||||
return {
|
|
||||||
countryCode: "",
|
|
||||||
displayName: "",
|
|
||||||
domains: [],
|
|
||||||
id: "",
|
|
||||||
tenantId: "",
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("test render", () => {
|
|
||||||
it("renders with no directories", () => {
|
|
||||||
const props = createBlankProps();
|
|
||||||
|
|
||||||
const wrapper = shallow(<DefaultDirectoryDropdownComponent {...props} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders with directories but no default", () => {
|
|
||||||
const props = createBlankProps();
|
|
||||||
const tenant1 = createBlankDirectory();
|
|
||||||
tenant1.displayName = "Microsoft";
|
|
||||||
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
|
|
||||||
const tenant2 = createBlankDirectory();
|
|
||||||
tenant1.displayName = "Macrohard";
|
|
||||||
tenant1.tenantId = "asdfghjklzxcvbnm9876543210";
|
|
||||||
props.directories = [tenant1, tenant2];
|
|
||||||
|
|
||||||
const wrapper = shallow(<DefaultDirectoryDropdownComponent {...props} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders with directories and default", () => {
|
|
||||||
const props = createBlankProps();
|
|
||||||
const tenant1 = createBlankDirectory();
|
|
||||||
tenant1.displayName = "Microsoft";
|
|
||||||
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
|
|
||||||
const tenant2 = createBlankDirectory();
|
|
||||||
tenant1.displayName = "Macrohard";
|
|
||||||
tenant1.tenantId = "asdfghjklzxcvbnm9876543210";
|
|
||||||
props.directories = [tenant1, tenant2];
|
|
||||||
|
|
||||||
props.defaultDirectoryId = "asdfghjklzxcvbnm9876543210";
|
|
||||||
|
|
||||||
const wrapper = shallow(<DefaultDirectoryDropdownComponent {...props} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders with directories and last visit default", () => {
|
|
||||||
const props = createBlankProps();
|
|
||||||
const tenant1 = createBlankDirectory();
|
|
||||||
tenant1.displayName = "Microsoft";
|
|
||||||
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
|
|
||||||
const tenant2 = createBlankDirectory();
|
|
||||||
tenant1.displayName = "Macrohard";
|
|
||||||
tenant1.tenantId = "asdfghjklzxcvbnm9876543210";
|
|
||||||
props.directories = [tenant1, tenant2];
|
|
||||||
|
|
||||||
props.defaultDirectoryId = "lastVisited";
|
|
||||||
|
|
||||||
const wrapper = shallow(<DefaultDirectoryDropdownComponent {...props} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("test function", () => {
|
|
||||||
it("on default directory change", () => {
|
|
||||||
const props = createBlankProps();
|
|
||||||
const tenant1 = createBlankDirectory();
|
|
||||||
tenant1.displayName = "Microsoft";
|
|
||||||
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
|
|
||||||
const tenant2 = createBlankDirectory();
|
|
||||||
tenant1.displayName = "Macrohard";
|
|
||||||
tenant1.tenantId = "asdfghjklzxcvbnm9876543210";
|
|
||||||
props.directories = [tenant1, tenant2];
|
|
||||||
props.defaultDirectoryId = "lastVisited";
|
|
||||||
|
|
||||||
const wrapper = mount(<DefaultDirectoryDropdownComponent {...props} />);
|
|
||||||
|
|
||||||
wrapper.find("div.defaultDirectoryDropdown").find("div.ms-Dropdown").simulate("click");
|
|
||||||
expect(wrapper.exists("div.ms-Callout-main")).toBe(true);
|
|
||||||
wrapper.find("button.ms-Dropdown-item").at(1).simulate("click");
|
|
||||||
expect(props.onDefaultDirectoryChange).toBeCalled();
|
|
||||||
expect(props.onDefaultDirectoryChange).toHaveBeenCalled();
|
|
||||||
|
|
||||||
wrapper.find("div.defaultDirectoryDropdown").find("div.ms-Dropdown").simulate("click");
|
|
||||||
expect(wrapper.exists("div.ms-Callout-main")).toBe(true);
|
|
||||||
wrapper.find("button.ms-Dropdown-item").at(0).simulate("click");
|
|
||||||
expect(props.onDefaultDirectoryChange).toBeCalled();
|
|
||||||
expect(props.onDefaultDirectoryChange).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
/**
|
|
||||||
* React component for Switch Directory
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Dropdown, IDropdownOption, IDropdownProps } from "@fluentui/react";
|
|
||||||
import * as React from "react";
|
|
||||||
import _ from "underscore";
|
|
||||||
import { Tenant } from "../../../Contracts/DataModels";
|
|
||||||
|
|
||||||
export interface DefaultDirectoryDropdownProps {
|
|
||||||
directories: Array<Tenant>;
|
|
||||||
defaultDirectoryId: string;
|
|
||||||
onDefaultDirectoryChange: (newDirectory: Tenant) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DefaultDirectoryDropdownComponent extends React.Component<DefaultDirectoryDropdownProps> {
|
|
||||||
public static readonly lastVisitedKey: string = "lastVisited";
|
|
||||||
|
|
||||||
public render(): JSX.Element {
|
|
||||||
const lastVisitedOption: IDropdownOption = {
|
|
||||||
key: DefaultDirectoryDropdownComponent.lastVisitedKey,
|
|
||||||
text: "Sign in to your last visited directory",
|
|
||||||
};
|
|
||||||
const directoryOptions: Array<IDropdownOption> = this.props.directories.map(
|
|
||||||
(dirc): IDropdownOption => {
|
|
||||||
return {
|
|
||||||
key: dirc.tenantId,
|
|
||||||
text: `${dirc.displayName}(${dirc.tenantId})`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const dropDownOptions: Array<IDropdownOption> = [lastVisitedOption, ...directoryOptions];
|
|
||||||
const dropDownProps: IDropdownProps = {
|
|
||||||
label: "Set your default directory",
|
|
||||||
options: dropDownOptions,
|
|
||||||
defaultSelectedKey: this.props.defaultDirectoryId ? this.props.defaultDirectoryId : lastVisitedOption.key,
|
|
||||||
onChange: this._onDropdownChange,
|
|
||||||
className: "defaultDirectoryDropdown",
|
|
||||||
};
|
|
||||||
|
|
||||||
return <Dropdown {...dropDownProps} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _onDropdownChange = (e: React.FormEvent<HTMLDivElement>, option?: IDropdownOption, index?: number): void => {
|
|
||||||
if (!option || !option.key) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (option.key === this.props.defaultDirectoryId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (option.key === DefaultDirectoryDropdownComponent.lastVisitedKey) {
|
|
||||||
this.props.onDefaultDirectoryChange({
|
|
||||||
tenantId: option.key,
|
|
||||||
countryCode: undefined,
|
|
||||||
displayName: undefined,
|
|
||||||
domains: [],
|
|
||||||
id: undefined,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedDirectory = _.find(this.props.directories, (d) => d.tenantId === option.key);
|
|
||||||
if (!selectedDirectory) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.onDefaultDirectoryChange(selectedDirectory);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
import * as React from "react";
|
|
||||||
import { DirectoryListComponent, DirectoryListProps } from "./DirectoryListComponent";
|
|
||||||
import { DefaultDirectoryDropdownComponent, DefaultDirectoryDropdownProps } from "./DefaultDirectoryDropdownComponent";
|
|
||||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
|
||||||
|
|
||||||
export class DirectoryComponentAdapter implements ReactAdapter {
|
|
||||||
public parameters: ko.Observable<number>;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private _dropdownProps: ko.Observable<DefaultDirectoryDropdownProps>,
|
|
||||||
private _listProps: ko.Observable<DirectoryListProps>
|
|
||||||
) {
|
|
||||||
this._dropdownProps.subscribe(() => this.forceRender());
|
|
||||||
this._listProps.subscribe(() => this.forceRender());
|
|
||||||
this.parameters = ko.observable<number>(Date.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className="directoryDropdownContainer">
|
|
||||||
<DefaultDirectoryDropdownComponent {...this._dropdownProps()} />
|
|
||||||
</div>
|
|
||||||
<div className="directoryDivider" />
|
|
||||||
<div className="directoryListContainer">
|
|
||||||
<DirectoryListComponent {...this._listProps()} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public forceRender(): void {
|
|
||||||
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { shallow, mount } from "enzyme";
|
|
||||||
import { DirectoryListComponent, DirectoryListProps } from "./DirectoryListComponent";
|
|
||||||
import { Tenant } from "../../../Contracts/DataModels";
|
|
||||||
|
|
||||||
const createBlankProps = (): DirectoryListProps => {
|
|
||||||
return {
|
|
||||||
selectedDirectoryId: undefined,
|
|
||||||
directories: [],
|
|
||||||
onNewDirectorySelected: jest.fn(),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const createBlankDirectory = (): Tenant => {
|
|
||||||
return {
|
|
||||||
countryCode: undefined,
|
|
||||||
displayName: undefined,
|
|
||||||
domains: [],
|
|
||||||
id: undefined,
|
|
||||||
tenantId: undefined,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("test render", () => {
|
|
||||||
it("renders with no directories", () => {
|
|
||||||
const props = createBlankProps();
|
|
||||||
|
|
||||||
const wrapper = shallow(<DirectoryListComponent {...props} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders with directories and selected", () => {
|
|
||||||
const props = createBlankProps();
|
|
||||||
const tenant1 = createBlankDirectory();
|
|
||||||
tenant1.displayName = "Microsoft";
|
|
||||||
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
|
|
||||||
const tenant2 = createBlankDirectory();
|
|
||||||
tenant1.displayName = "Macrohard";
|
|
||||||
tenant1.tenantId = "asdfghjklzxcvbnm9876543210";
|
|
||||||
props.directories = [tenant1, tenant2];
|
|
||||||
|
|
||||||
props.selectedDirectoryId = "asdfghjklzxcvbnm9876543210";
|
|
||||||
|
|
||||||
const wrapper = shallow(<DirectoryListComponent {...props} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders with filters", () => {
|
|
||||||
const props = createBlankProps();
|
|
||||||
const tenant1 = createBlankDirectory();
|
|
||||||
tenant1.displayName = "Microsoft";
|
|
||||||
tenant1.tenantId = "1234567890";
|
|
||||||
const tenant2 = createBlankDirectory();
|
|
||||||
tenant1.displayName = "Macrohard";
|
|
||||||
tenant1.tenantId = "9876543210";
|
|
||||||
props.directories = [tenant1, tenant2];
|
|
||||||
props.selectedDirectoryId = "9876543210";
|
|
||||||
|
|
||||||
const wrapper = mount(<DirectoryListComponent {...props} />);
|
|
||||||
wrapper.find("input.ms-TextField-field").simulate("change", { target: { value: "Macro" } });
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("test function", () => {
|
|
||||||
it("on new directory selected", () => {
|
|
||||||
const props = createBlankProps();
|
|
||||||
const tenant1 = createBlankDirectory();
|
|
||||||
tenant1.displayName = "Microsoft";
|
|
||||||
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
|
|
||||||
props.directories = [tenant1];
|
|
||||||
|
|
||||||
const wrapper = mount(<DirectoryListComponent {...props} />);
|
|
||||||
wrapper.find("button.directoryListButton").simulate("click");
|
|
||||||
expect(props.onNewDirectorySelected).toBeCalled();
|
|
||||||
expect(props.onNewDirectorySelected).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
import {
|
|
||||||
DefaultButton,
|
|
||||||
IButtonProps,
|
|
||||||
ITextFieldProps,
|
|
||||||
List,
|
|
||||||
ScrollablePane,
|
|
||||||
Sticky,
|
|
||||||
StickyPositionType,
|
|
||||||
TextField,
|
|
||||||
} from "@fluentui/react";
|
|
||||||
import * as React from "react";
|
|
||||||
import _ from "underscore";
|
|
||||||
import { Tenant } from "../../../Contracts/DataModels";
|
|
||||||
|
|
||||||
export interface DirectoryListProps {
|
|
||||||
directories: Array<Tenant>;
|
|
||||||
selectedDirectoryId: string;
|
|
||||||
onNewDirectorySelected: (newDirectory: Tenant) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DirectoryListComponentState {
|
|
||||||
filterText: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// onRenderCell is not called when selectedDirectoryId changed, so add a selected state to force render
|
|
||||||
interface ListTenant extends Tenant {
|
|
||||||
selected?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DirectoryListComponent extends React.Component<DirectoryListProps, DirectoryListComponentState> {
|
|
||||||
constructor(props: DirectoryListProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
filterText: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(): JSX.Element {
|
|
||||||
const { directories: originalItems, selectedDirectoryId } = this.props;
|
|
||||||
const { filterText } = this.state;
|
|
||||||
const filteredItems =
|
|
||||||
originalItems && originalItems.length && filterText
|
|
||||||
? originalItems.filter(
|
|
||||||
(directory) =>
|
|
||||||
directory.displayName &&
|
|
||||||
directory.displayName.toLowerCase().indexOf(filterText && filterText.toLowerCase()) >= 0
|
|
||||||
)
|
|
||||||
: originalItems;
|
|
||||||
const filteredItemsSelected = filteredItems.map((t) => {
|
|
||||||
let tenant: ListTenant = t;
|
|
||||||
tenant.selected = t.tenantId === selectedDirectoryId;
|
|
||||||
return tenant;
|
|
||||||
});
|
|
||||||
|
|
||||||
const textFieldProps: ITextFieldProps = {
|
|
||||||
className: "directoryListFilterTextBox",
|
|
||||||
placeholder: "Filter by directory name",
|
|
||||||
onChange: this._onFilterChanged,
|
|
||||||
ariaLabel: "Directory filter text box",
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: add magnify glass to search bar with onRenderSuffix
|
|
||||||
return (
|
|
||||||
<ScrollablePane data-is-scrollable="true">
|
|
||||||
<Sticky stickyPosition={StickyPositionType.Header}>
|
|
||||||
<TextField {...textFieldProps} />
|
|
||||||
</Sticky>
|
|
||||||
<List items={filteredItemsSelected} onRenderCell={this._onRenderCell} />
|
|
||||||
</ScrollablePane>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _onFilterChanged = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, text?: string): void => {
|
|
||||||
this.setState({
|
|
||||||
filterText: text,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
private _onRenderCell = (directory: ListTenant): JSX.Element => {
|
|
||||||
const buttonProps: IButtonProps = {
|
|
||||||
disabled: directory.selected || false,
|
|
||||||
className: "directoryListButton",
|
|
||||||
onClick: this._onNewDirectoryClick,
|
|
||||||
styles: {
|
|
||||||
root: {
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
height: "auto",
|
|
||||||
borderBottom: "1px solid #ccc",
|
|
||||||
padding: "1px 0",
|
|
||||||
width: "100%",
|
|
||||||
},
|
|
||||||
rootDisabled: {
|
|
||||||
backgroundColor: "#f1f1f8",
|
|
||||||
},
|
|
||||||
rootHovered: {
|
|
||||||
backgroundColor: "rgba(85,179,255,.1)",
|
|
||||||
},
|
|
||||||
flexContainer: {
|
|
||||||
height: "auto",
|
|
||||||
justifyContent: "flex-start",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DefaultButton {...buttonProps}>
|
|
||||||
<div className="directoryListItem" data-is-focusable={true}>
|
|
||||||
<div className="directoryListItemName">{directory.displayName}</div>
|
|
||||||
<div className="directoryListItemId">{directory.tenantId}</div>
|
|
||||||
</div>
|
|
||||||
</DefaultButton>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
private _onNewDirectoryClick = (e: React.MouseEvent<HTMLButtonElement>): void => {
|
|
||||||
if (!e || !e.currentTarget) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const buttonElement = e.currentTarget;
|
|
||||||
const selectedDirectoryId = buttonElement.getElementsByClassName("directoryListItemId")[0].textContent;
|
|
||||||
const selectedDirectory = _.find(this.props.directories, (d) => d.tenantId === selectedDirectoryId);
|
|
||||||
|
|
||||||
this.props.onNewDirectorySelected(selectedDirectory);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`test render renders with directories and default 1`] = `
|
|
||||||
<Dropdown
|
|
||||||
className="defaultDirectoryDropdown"
|
|
||||||
defaultSelectedKey="asdfghjklzxcvbnm9876543210"
|
|
||||||
label="Set your default directory"
|
|
||||||
onChange={[Function]}
|
|
||||||
options={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"key": "lastVisited",
|
|
||||||
"text": "Sign in to your last visited directory",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"key": "asdfghjklzxcvbnm9876543210",
|
|
||||||
"text": "Macrohard(asdfghjklzxcvbnm9876543210)",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"key": "",
|
|
||||||
"text": "()",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`test render renders with directories and last visit default 1`] = `
|
|
||||||
<Dropdown
|
|
||||||
className="defaultDirectoryDropdown"
|
|
||||||
defaultSelectedKey="lastVisited"
|
|
||||||
label="Set your default directory"
|
|
||||||
onChange={[Function]}
|
|
||||||
options={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"key": "lastVisited",
|
|
||||||
"text": "Sign in to your last visited directory",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"key": "asdfghjklzxcvbnm9876543210",
|
|
||||||
"text": "Macrohard(asdfghjklzxcvbnm9876543210)",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"key": "",
|
|
||||||
"text": "()",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`test render renders with directories but no default 1`] = `
|
|
||||||
<Dropdown
|
|
||||||
className="defaultDirectoryDropdown"
|
|
||||||
defaultSelectedKey="lastVisited"
|
|
||||||
label="Set your default directory"
|
|
||||||
onChange={[Function]}
|
|
||||||
options={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"key": "lastVisited",
|
|
||||||
"text": "Sign in to your last visited directory",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"key": "asdfghjklzxcvbnm9876543210",
|
|
||||||
"text": "Macrohard(asdfghjklzxcvbnm9876543210)",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"key": "",
|
|
||||||
"text": "()",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`test render renders with no directories 1`] = `
|
|
||||||
<Dropdown
|
|
||||||
className="defaultDirectoryDropdown"
|
|
||||||
defaultSelectedKey="lastVisited"
|
|
||||||
label="Set your default directory"
|
|
||||||
onChange={[Function]}
|
|
||||||
options={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"key": "lastVisited",
|
|
||||||
"text": "Sign in to your last visited directory",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
import { DynamicListComponent, DynamicListParams, DynamicListItem } from "./DynamicListComponent";
|
|
||||||
|
|
||||||
const $ = (selector: string) => document.querySelector(selector) as HTMLElement;
|
|
||||||
|
|
||||||
function buildComponent(buttonOptions: any) {
|
|
||||||
document.body.innerHTML = DynamicListComponent.template as any;
|
|
||||||
const vm = new DynamicListComponent.viewModel(buttonOptions);
|
|
||||||
ko.applyBindings(vm);
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("Dynamic List Component", () => {
|
|
||||||
const mockPlaceHolder = "Write here";
|
|
||||||
const mockButton = "Add something";
|
|
||||||
const mockValue = "/someText";
|
|
||||||
const mockAriaLabel = "Add ariaLabel";
|
|
||||||
const items: ko.ObservableArray<DynamicListItem> = ko.observableArray<DynamicListItem>();
|
|
||||||
|
|
||||||
function buildListOptions(
|
|
||||||
items: ko.ObservableArray<DynamicListItem>,
|
|
||||||
placeholder?: string,
|
|
||||||
mockButton?: string
|
|
||||||
): DynamicListParams {
|
|
||||||
return {
|
|
||||||
placeholder: placeholder,
|
|
||||||
listItems: items,
|
|
||||||
buttonText: mockButton,
|
|
||||||
ariaLabel: mockAriaLabel,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
ko.cleanNode(document);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Rendering", () => {
|
|
||||||
it("should display button text", () => {
|
|
||||||
const params = buildListOptions(items, mockPlaceHolder, mockButton);
|
|
||||||
buildComponent(params);
|
|
||||||
expect($(".dynamicListItemAdd").textContent).toContain(mockButton);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Behavior", () => {
|
|
||||||
it("should add items to the list", () => {
|
|
||||||
const params = buildListOptions(items, mockPlaceHolder, mockButton);
|
|
||||||
buildComponent(params);
|
|
||||||
$(".dynamicListItemAdd").click();
|
|
||||||
expect(items().length).toBe(1);
|
|
||||||
const input = document.getElementsByClassName("dynamicListItem").item(0).children[0];
|
|
||||||
input.setAttribute("value", mockValue);
|
|
||||||
input.dispatchEvent(new Event("change"));
|
|
||||||
input.dispatchEvent(new Event("blur"));
|
|
||||||
expect(items()[0].value()).toBe(mockValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should remove items from the list", () => {
|
|
||||||
const params = buildListOptions(items, mockPlaceHolder);
|
|
||||||
buildComponent(params);
|
|
||||||
$(".dynamicListItemDelete").click();
|
|
||||||
expect(items().length).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
@import "../../../../less/Common/Constants";
|
|
||||||
|
|
||||||
.dynamicList {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.dynamicListContainer {
|
|
||||||
.dynamicListItem {
|
|
||||||
justify-content: space-around;
|
|
||||||
margin-bottom: @MediumSpace;
|
|
||||||
|
|
||||||
input {
|
|
||||||
width: @newCollectionPaneInputWidth;
|
|
||||||
margin: auto;
|
|
||||||
font-size: @mediumFontSize;
|
|
||||||
padding: @SmallSpace @DefaultSpace;
|
|
||||||
color: @BaseDark;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dynamicListItemDelete {
|
|
||||||
padding: @SmallSpace @SmallSpace @DefaultSpace;
|
|
||||||
margin-left: @SmallSpace;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.hover();
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
.active();
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
.dataExplorerIcons();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dynamicListItemNew {
|
|
||||||
margin-top: @LargeSpace;
|
|
||||||
|
|
||||||
.dynamicListItemAdd {
|
|
||||||
padding: @DefaultSpace;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.hover();
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
.active();
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
.dataExplorerIcons();
|
|
||||||
margin: 0px @SmallSpace @SmallSpace 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
/**
|
|
||||||
* Dynamic list:
|
|
||||||
*
|
|
||||||
* Creates a list of dynamic inputs that can be populated and deleted.
|
|
||||||
*
|
|
||||||
* How to use in your markup:
|
|
||||||
* <dynamic-list params="{ listItems: anObservableArrayOfDynamicListItem, placeholder: 'Text to display in placeholder', ariaLabel: 'Text for aria-label', buttonText: 'Add item' }">
|
|
||||||
* </dynamic-list>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as ko from "knockout";
|
|
||||||
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
|
|
||||||
import { KeyCodes } from "../../../Common/Constants";
|
|
||||||
import template from "./dynamic-list.html";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parameters for this component
|
|
||||||
*/
|
|
||||||
export interface DynamicListParams {
|
|
||||||
/**
|
|
||||||
* Observable list of items to update
|
|
||||||
*/
|
|
||||||
listItems: ko.ObservableArray<DynamicListItem>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Placeholder text to use on inputs
|
|
||||||
*/
|
|
||||||
placeholder?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Text to use as aria-label
|
|
||||||
*/
|
|
||||||
ariaLabel: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Text for the button to add items
|
|
||||||
*/
|
|
||||||
buttonText?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback triggered when the template is bound to the component (for testing purposes)
|
|
||||||
*/
|
|
||||||
onTemplateReady?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Item in the dynamic list
|
|
||||||
*/
|
|
||||||
export interface DynamicListItem {
|
|
||||||
value: ko.Observable<string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DynamicListViewModel extends WaitsForTemplateViewModel {
|
|
||||||
public placeholder: string;
|
|
||||||
public ariaLabel: string;
|
|
||||||
public buttonText: string;
|
|
||||||
public newItem: ko.Observable<string>;
|
|
||||||
public isTemplateReady: ko.Observable<boolean>;
|
|
||||||
public listItems: ko.ObservableArray<DynamicListItem>;
|
|
||||||
|
|
||||||
public constructor(options: DynamicListParams) {
|
|
||||||
super();
|
|
||||||
super.onTemplateReady((isTemplateReady: boolean) => {
|
|
||||||
if (isTemplateReady && options.onTemplateReady) {
|
|
||||||
options.onTemplateReady();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const params: DynamicListParams = options;
|
|
||||||
const paramsPlaceholder: string = params.placeholder;
|
|
||||||
const paramsButtonText: string = params.buttonText;
|
|
||||||
this.placeholder = paramsPlaceholder || "Write a value";
|
|
||||||
this.ariaLabel = "Unique keys";
|
|
||||||
this.buttonText = paramsButtonText || "Add item";
|
|
||||||
this.listItems = params.listItems || ko.observableArray<DynamicListItem>();
|
|
||||||
this.newItem = ko.observable("");
|
|
||||||
}
|
|
||||||
|
|
||||||
public removeItem = (data: any, event: MouseEvent | KeyboardEvent): void => {
|
|
||||||
const context = ko.contextFor(event.target as Node);
|
|
||||||
this.listItems.splice(context.$index(), 1);
|
|
||||||
document.getElementById("addUniqueKeyBtn").focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
public onRemoveItemKeyPress = (data: any, event: KeyboardEvent, source: any): boolean => {
|
|
||||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
|
||||||
this.removeItem(data, event);
|
|
||||||
(document.querySelector(".dynamicListItem:last-of-type input") as HTMLElement).focus();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
public addItem(): void {
|
|
||||||
this.listItems.push({ value: ko.observable("") });
|
|
||||||
(document.querySelector(".dynamicListItem:last-of-type input") as HTMLElement).focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
public onAddItemKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
|
||||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
|
||||||
this.addItem();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class for ko component registration
|
|
||||||
*/
|
|
||||||
export const DynamicListComponent = {
|
|
||||||
viewModel: DynamicListViewModel,
|
|
||||||
template,
|
|
||||||
};
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<div class="dynamicList" data-bind="setTemplateReady: true">
|
|
||||||
<div class="dynamicListContainer" data-bind="foreach: listItems">
|
|
||||||
<div class="dynamicListItem">
|
|
||||||
<input
|
|
||||||
id="uniqueKeyItems"
|
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
data-bind="value: value, attr: {placeholder: $parent.placeholder, 'aria-label': $parent.ariaLabel}"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
class="dynamicListItemDelete"
|
|
||||||
title="Remove item"
|
|
||||||
role="button"
|
|
||||||
aria-label="Remove item"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="click: $parent.removeItem, event: { keydown: $parent.onRemoveItemKeyPress }"
|
|
||||||
>
|
|
||||||
<img src="/delete.svg" alt="Remove item" />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="dynamicListItemNew">
|
|
||||||
<span
|
|
||||||
class="dynamicListItemAdd"
|
|
||||||
id="addUniqueKeyBtn"
|
|
||||||
role="button"
|
|
||||||
aria-label="Add unique key"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="click: addItem, event: { keydown: onAddItemKeyPress }"
|
|
||||||
>
|
|
||||||
<img src="/Add-property.svg" data-bind="attr: {alt: buttonText}" /> <span data-bind="text: buttonText"></span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
|
import { Spinner, SpinnerSize } from "@fluentui/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { loadMonaco, monaco } from "../../LazyMonaco";
|
import { loadMonaco, monaco } from "../../LazyMonaco";
|
||||||
|
// import "./EditorReact.less";
|
||||||
|
|
||||||
|
interface EditorReactStates {
|
||||||
|
showEditor: boolean;
|
||||||
|
}
|
||||||
export interface EditorReactProps {
|
export interface EditorReactProps {
|
||||||
language: string;
|
language: string;
|
||||||
content: string;
|
content: string;
|
||||||
@@ -12,22 +17,26 @@ export interface EditorReactProps {
|
|||||||
theme?: string; // Monaco editor theme
|
theme?: string; // Monaco editor theme
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EditorReact extends React.Component<EditorReactProps> {
|
export class EditorReact extends React.Component<EditorReactProps, EditorReactStates> {
|
||||||
private rootNode: HTMLElement;
|
private rootNode: HTMLElement;
|
||||||
private editor: monaco.editor.IStandaloneCodeEditor;
|
private editor: monaco.editor.IStandaloneCodeEditor;
|
||||||
private selectionListener: monaco.IDisposable;
|
private selectionListener: monaco.IDisposable;
|
||||||
|
|
||||||
public constructor(props: EditorReactProps) {
|
public constructor(props: EditorReactProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
showEditor: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount(): void {
|
public componentDidMount(): void {
|
||||||
this.createEditor(this.configureEditor.bind(this));
|
this.createEditor(this.configureEditor.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public shouldComponentUpdate(): boolean {
|
public componentDidUpdate(previous: EditorReactProps) {
|
||||||
// Prevents component re-rendering
|
if (this.props.content !== previous.content) {
|
||||||
return false;
|
this.editor.setValue(this.props.content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillUnmount(): void {
|
public componentWillUnmount(): void {
|
||||||
@@ -35,14 +44,19 @@ export class EditorReact extends React.Component<EditorReactProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
return <div className="jsonEditor" ref={(elt: HTMLElement) => this.setRef(elt)} />;
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{!this.state.showEditor && <Spinner size={SpinnerSize.large} className="spinner" />}
|
||||||
|
<div className="jsonEditor" ref={(elt: HTMLElement) => this.setRef(elt)} />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
|
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
|
||||||
this.editor = editor;
|
this.editor = editor;
|
||||||
const queryEditorModel = this.editor.getModel();
|
const queryEditorModel = this.editor.getModel();
|
||||||
if (!this.props.isReadOnly && this.props.onContentChanged) {
|
if (!this.props.isReadOnly && this.props.onContentChanged) {
|
||||||
queryEditorModel.onDidChangeContent((e: monaco.editor.IModelContentChangedEvent) => {
|
queryEditorModel.onDidChangeContent(() => {
|
||||||
const queryEditorModel = this.editor.getModel();
|
const queryEditorModel = this.editor.getModel();
|
||||||
this.props.onContentChanged(queryEditorModel.getValue());
|
this.props.onContentChanged(queryEditorModel.getValue());
|
||||||
});
|
});
|
||||||
@@ -76,6 +90,12 @@ export class EditorReact extends React.Component<EditorReactProps> {
|
|||||||
this.rootNode.innerHTML = "";
|
this.rootNode.innerHTML = "";
|
||||||
const monaco = await loadMonaco();
|
const monaco = await loadMonaco();
|
||||||
createCallback(monaco.editor.create(this.rootNode, options));
|
createCallback(monaco.editor.create(this.rootNode, options));
|
||||||
|
|
||||||
|
if (this.rootNode.innerHTML) {
|
||||||
|
this.setState({
|
||||||
|
showEditor: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setRef(element: HTMLElement): void {
|
private setRef(element: HTMLElement): void {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export class GitHubReposComponent extends React.Component<GitHubReposComponentPr
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={"paneMainContent"}>{content}</div>
|
<div>{content}</div>
|
||||||
{!this.props.showAuthorizeAccess && (
|
{!this.props.showAuthorizeAccess && (
|
||||||
<>
|
<>
|
||||||
<div className={"paneFooter"} style={ContentFooterStyle}>
|
<div className={"paneFooter"} style={ContentFooterStyle}>
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import ko from "knockout";
|
|
||||||
import * as React from "react";
|
|
||||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
|
||||||
import { GitHubReposComponent, GitHubReposComponentProps } from "./GitHubReposComponent";
|
|
||||||
|
|
||||||
export class GitHubReposComponentAdapter implements ReactAdapter {
|
|
||||||
public parameters: ko.Observable<number>;
|
|
||||||
|
|
||||||
constructor(private props: GitHubReposComponentProps) {
|
|
||||||
this.parameters = ko.observable<number>(Date.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
|
||||||
return <GitHubReposComponent {...this.props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
public triggerRender(): void {
|
|
||||||
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,12 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
.input-type-head-text-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.input-query-form {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
textarea {
|
textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
@@ -21,4 +27,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.input-typeahead-chocies-container {
|
||||||
|
border: 1px solid lightgrey;
|
||||||
|
padding: 5px 10px 5px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
.choice-caption{
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,14 +6,13 @@
|
|||||||
* typeaheadOverrideOptions: { dynamic:false }
|
* typeaheadOverrideOptions: { dynamic:false }
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import "jquery-typeahead";
|
import { getTheme, IconButton, IIconProps, List, Stack, TextField } from "@fluentui/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { KeyCodes } from "../../../Common/Constants";
|
|
||||||
import "./InputTypeahead.less";
|
import "./InputTypeahead.less";
|
||||||
|
|
||||||
export interface Item {
|
export interface Item {
|
||||||
caption: string;
|
caption: string;
|
||||||
value: any;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,170 +74,128 @@ export interface InputTypeaheadComponentProps {
|
|||||||
useTextarea?: boolean;
|
useTextarea?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OnClickItem {
|
interface InputTypeaheadComponentState {
|
||||||
matchedKey: string;
|
isSuggestionVisible: boolean;
|
||||||
value: any;
|
selectedChoice: Item;
|
||||||
caption: string;
|
filteredChoices: Item[];
|
||||||
group: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Cache {
|
|
||||||
inputValue: string;
|
|
||||||
selection: Item;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface InputTypeaheadComponentState {}
|
|
||||||
|
|
||||||
export class InputTypeaheadComponent extends React.Component<
|
export class InputTypeaheadComponent extends React.Component<
|
||||||
InputTypeaheadComponentProps,
|
InputTypeaheadComponentProps,
|
||||||
InputTypeaheadComponentState
|
InputTypeaheadComponentState
|
||||||
> {
|
> {
|
||||||
private inputElt: HTMLElement;
|
constructor(props: InputTypeaheadComponentProps) {
|
||||||
private containerElt: HTMLElement;
|
|
||||||
|
|
||||||
private cache: Cache;
|
|
||||||
private inputValue: string;
|
|
||||||
private selection: Item;
|
|
||||||
|
|
||||||
public constructor(props: InputTypeaheadComponentProps) {
|
|
||||||
super(props);
|
super(props);
|
||||||
this.cache = {
|
this.state = {
|
||||||
inputValue: null,
|
isSuggestionVisible: false,
|
||||||
selection: null,
|
filteredChoices: [],
|
||||||
|
selectedChoice: {
|
||||||
|
caption: "",
|
||||||
|
value: "",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private onRenderCell = (item: Item): JSX.Element => {
|
||||||
* Props have changed
|
|
||||||
* @param prevProps
|
|
||||||
* @param prevState
|
|
||||||
* @param snapshot
|
|
||||||
*/
|
|
||||||
public componentDidUpdate(
|
|
||||||
prevProps: InputTypeaheadComponentProps,
|
|
||||||
prevState: InputTypeaheadComponentState,
|
|
||||||
snapshot: any
|
|
||||||
): void {
|
|
||||||
if (prevProps.defaultValue !== this.props.defaultValue) {
|
|
||||||
$(this.inputElt).val(this.props.defaultValue);
|
|
||||||
this.initializeTypeahead();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executed once react is done building the DOM for this component
|
|
||||||
*/
|
|
||||||
public componentDidMount(): void {
|
|
||||||
this.initializeTypeahead();
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(): JSX.Element {
|
|
||||||
return (
|
return (
|
||||||
<span className="input-typeahead-container">
|
<div className="input-typeahead-chocies-container" onClick={() => this.onChoiceClick(item)}>
|
||||||
<div
|
<p className="choice-caption">{item.caption}</p>
|
||||||
className="input-typehead"
|
<span>{item.value}</span>
|
||||||
onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onKeyDown(event)}
|
|
||||||
>
|
|
||||||
<div className="typeahead__container" ref={(input) => (this.containerElt = input)}>
|
|
||||||
<div className="typeahead__field">
|
|
||||||
<span className="typeahead__query">
|
|
||||||
{this.props.useTextarea ? (
|
|
||||||
<textarea
|
|
||||||
rows={1}
|
|
||||||
name="q"
|
|
||||||
autoComplete="off"
|
|
||||||
aria-label="Input query"
|
|
||||||
ref={(input) => (this.inputElt = input)}
|
|
||||||
defaultValue={this.props.defaultValue}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<input
|
|
||||||
name="q"
|
|
||||||
type="search"
|
|
||||||
autoComplete="off"
|
|
||||||
aria-label="Input query"
|
|
||||||
ref={(input) => (this.inputElt = input)}
|
|
||||||
defaultValue={this.props.defaultValue}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
{this.props.showSearchButton && (
|
|
||||||
<span className="typeahead__button">
|
|
||||||
<button type="submit">
|
|
||||||
<span className="typeahead__search-icon" />
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
private onKeyDown(event: React.KeyboardEvent<HTMLElement>) {
|
private onChoiceClick = (item: Item): void => {
|
||||||
if (event.keyCode === KeyCodes.Enter) {
|
this.props.onNewValue(item.caption);
|
||||||
|
this.setState({ isSuggestionVisible: false, selectedChoice: item });
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleChange = (value: string): void => {
|
||||||
|
if (!value) {
|
||||||
|
this.setState({ isSuggestionVisible: true });
|
||||||
|
}
|
||||||
|
this.props.onNewValue(value);
|
||||||
|
const filteredChoices = this.filterChoiceByValue(this.props.choices, value);
|
||||||
|
this.setState({ filteredChoices });
|
||||||
|
};
|
||||||
|
|
||||||
|
private onSubmit = (event: React.KeyboardEvent<HTMLElement>): void => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
if (this.props.submitFct) {
|
if (this.props.submitFct) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
this.props.submitFct(this.cache.inputValue, this.cache.selection);
|
this.props.submitFct(this.props.defaultValue, this.state.selectedChoice);
|
||||||
$(this.containerElt).children(".typeahead__result").hide();
|
this.setState({ isSuggestionVisible: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Must execute once ko is rendered, so that it can find the input element by id
|
|
||||||
*/
|
|
||||||
private initializeTypeahead(): void {
|
|
||||||
const props = this.props;
|
|
||||||
let cache = this.cache;
|
|
||||||
let options: any = {
|
|
||||||
input: this.inputElt,
|
|
||||||
order: "asc",
|
|
||||||
minLength: 0,
|
|
||||||
searchOnFocus: true,
|
|
||||||
source: {
|
|
||||||
display: "caption",
|
|
||||||
data: () => {
|
|
||||||
return props.choices;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
callback: {
|
|
||||||
onClick: (node: any, a: any, item: OnClickItem, event: any) => {
|
|
||||||
cache.selection = item;
|
|
||||||
|
|
||||||
if (props.onSelected) {
|
|
||||||
props.onSelected(item);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onResult(node: any, query: any, result: any, resultCount: any, resultCountPerGroup: any) {
|
|
||||||
cache.inputValue = query;
|
|
||||||
if (props.onNewValue) {
|
|
||||||
props.onNewValue(query);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
template: (query: string, item: any) => {
|
|
||||||
// Don't display id if caption *IS* the id
|
|
||||||
return item.caption === item.value
|
|
||||||
? "<span>{{caption}}</span>"
|
|
||||||
: "<span><div>{{caption}}</div><div><small>{{value}}</small></div></span>";
|
|
||||||
},
|
|
||||||
dynamic: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Override options
|
private filterChoiceByValue = (choices: Item[], searchKeyword: string): Item[] => {
|
||||||
if (props.typeaheadOverrideOptions) {
|
return choices.filter((choice) =>
|
||||||
for (const p in props.typeaheadOverrideOptions) {
|
// @ts-ignore
|
||||||
options[p] = props.typeaheadOverrideOptions[p];
|
Object.keys(choice).some((key) => choice[key].toLowerCase().includes(searchKeyword.toLowerCase()))
|
||||||
}
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
if (props.hasOwnProperty("showCancelButton")) {
|
public render(): JSX.Element {
|
||||||
options.cancelButton = props.showCancelButton;
|
const { defaultValue, useTextarea, placeholder, onNewValue } = this.props;
|
||||||
}
|
const { isSuggestionVisible, selectedChoice, filteredChoices } = this.state;
|
||||||
|
const theme = getTheme();
|
||||||
|
|
||||||
$(this.inputElt).typeahead(options);
|
const iconButtonStyles = {
|
||||||
|
root: {
|
||||||
|
color: theme.palette.neutralPrimary,
|
||||||
|
marginLeft: "10px !important",
|
||||||
|
marginTop: "0px",
|
||||||
|
marginRight: "2px",
|
||||||
|
width: "42px",
|
||||||
|
},
|
||||||
|
rootHovered: {
|
||||||
|
color: theme.palette.neutralDark,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const cancelIcon: IIconProps = { iconName: "cancel" };
|
||||||
|
const searchIcon: IIconProps = { iconName: "Search" };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="input-typeahead-container">
|
||||||
|
<Stack horizontal>
|
||||||
|
<form aria-labelledby="input" className="input-query-form">
|
||||||
|
<TextField
|
||||||
|
multiline={useTextarea}
|
||||||
|
rows={1}
|
||||||
|
id="input"
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
ariaLabel="Input query"
|
||||||
|
placeholder={placeholder}
|
||||||
|
className="input-type-head-text-field"
|
||||||
|
value={defaultValue}
|
||||||
|
onKeyDown={this.onSubmit}
|
||||||
|
onFocus={() => this.setState({ isSuggestionVisible: true })}
|
||||||
|
onChange={(_event, newValue?: string) => this.handleChange(newValue)}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
{this.props.showCancelButton && (
|
||||||
|
<IconButton
|
||||||
|
styles={iconButtonStyles}
|
||||||
|
iconProps={cancelIcon}
|
||||||
|
ariaLabel="cancel Button"
|
||||||
|
onClick={() => onNewValue("")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{this.props.showSearchButton && (
|
||||||
|
<IconButton
|
||||||
|
styles={iconButtonStyles}
|
||||||
|
iconProps={searchIcon}
|
||||||
|
ariaLabel="Search Button"
|
||||||
|
onClick={() => this.props.submitFct(defaultValue, selectedChoice)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
{filteredChoices.length && isSuggestionVisible ? (
|
||||||
|
<List items={filteredChoices} onRenderCell={this.onRenderCell} />
|
||||||
|
) : undefined}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,61 +1,55 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`inputTypeahead renders <input /> 1`] = `
|
exports[`inputTypeahead renders <input /> 1`] = `
|
||||||
<span
|
<div
|
||||||
className="input-typeahead-container"
|
className="input-typeahead-container"
|
||||||
>
|
>
|
||||||
<div
|
<Stack
|
||||||
className="input-typehead"
|
horizontal={true}
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
aria-labelledby="input"
|
||||||
|
className="input-query-form"
|
||||||
|
>
|
||||||
|
<StyledTextFieldBase
|
||||||
|
ariaLabel="Input query"
|
||||||
|
className="input-type-head-text-field"
|
||||||
|
id="input"
|
||||||
|
multiline={false}
|
||||||
|
onChange={[Function]}
|
||||||
|
onFocus={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
>
|
placeholder="placeholder"
|
||||||
<div
|
rows={1}
|
||||||
className="typeahead__container"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="typeahead__field"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="typeahead__query"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
aria-label="Input query"
|
|
||||||
autoComplete="off"
|
|
||||||
name="q"
|
|
||||||
type="search"
|
|
||||||
/>
|
/>
|
||||||
</span>
|
</form>
|
||||||
</div>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`inputTypeahead renders <textarea /> 1`] = `
|
exports[`inputTypeahead renders <textarea /> 1`] = `
|
||||||
<span
|
<div
|
||||||
className="input-typeahead-container"
|
className="input-typeahead-container"
|
||||||
>
|
>
|
||||||
<div
|
<Stack
|
||||||
className="input-typehead"
|
horizontal={true}
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
aria-labelledby="input"
|
||||||
|
className="input-query-form"
|
||||||
|
>
|
||||||
|
<StyledTextFieldBase
|
||||||
|
ariaLabel="Input query"
|
||||||
|
className="input-type-head-text-field"
|
||||||
|
id="input"
|
||||||
|
multiline={true}
|
||||||
|
onChange={[Function]}
|
||||||
|
onFocus={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
>
|
placeholder="placeholder"
|
||||||
<div
|
|
||||||
className="typeahead__container"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="typeahead__field"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="typeahead__query"
|
|
||||||
>
|
|
||||||
<textarea
|
|
||||||
aria-label="Input query"
|
|
||||||
autoComplete="off"
|
|
||||||
name="q"
|
|
||||||
rows={1}
|
rows={1}
|
||||||
/>
|
/>
|
||||||
</span>
|
</form>
|
||||||
</div>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,154 +1,90 @@
|
|||||||
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import { NotebookTerminalComponent } from "./NotebookTerminalComponent";
|
import { NotebookTerminalComponent, NotebookTerminalComponentProps } from "./NotebookTerminalComponent";
|
||||||
|
|
||||||
const createTestDatabaseAccount = (): DataModels.DatabaseAccount => {
|
const testAccount: DataModels.DatabaseAccount = {
|
||||||
return {
|
id: "id",
|
||||||
id: "testId",
|
kind: "kind",
|
||||||
kind: "testKind",
|
location: "location",
|
||||||
location: "testLocation",
|
name: "name",
|
||||||
name: "testName",
|
|
||||||
properties: {
|
properties: {
|
||||||
cassandraEndpoint: null,
|
|
||||||
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
|
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
|
||||||
gremlinEndpoint: null,
|
|
||||||
tableEndpoint: null,
|
|
||||||
},
|
},
|
||||||
type: "testType",
|
type: "type",
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createTestMongo32DatabaseAccount = (): DataModels.DatabaseAccount => {
|
const testMongo32Account: DataModels.DatabaseAccount = {
|
||||||
return {
|
...testAccount,
|
||||||
id: "testId",
|
|
||||||
kind: "testKind",
|
|
||||||
location: "testLocation",
|
|
||||||
name: "testName",
|
|
||||||
properties: {
|
|
||||||
cassandraEndpoint: null,
|
|
||||||
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
|
|
||||||
gremlinEndpoint: null,
|
|
||||||
tableEndpoint: null,
|
|
||||||
},
|
|
||||||
type: "testType",
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createTestMongo36DatabaseAccount = (): DataModels.DatabaseAccount => {
|
const testMongo36Account: DataModels.DatabaseAccount = {
|
||||||
return {
|
...testAccount,
|
||||||
id: "testId",
|
|
||||||
kind: "testKind",
|
|
||||||
location: "testLocation",
|
|
||||||
name: "testName",
|
|
||||||
properties: {
|
properties: {
|
||||||
cassandraEndpoint: null,
|
|
||||||
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
|
|
||||||
gremlinEndpoint: null,
|
|
||||||
tableEndpoint: null,
|
|
||||||
mongoEndpoint: "https://testMongoEndpoint.azure.com/",
|
mongoEndpoint: "https://testMongoEndpoint.azure.com/",
|
||||||
},
|
},
|
||||||
type: "testType",
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createTestCassandraDatabaseAccount = (): DataModels.DatabaseAccount => {
|
const testCassandraAccount: DataModels.DatabaseAccount = {
|
||||||
return {
|
...testAccount,
|
||||||
id: "testId",
|
|
||||||
kind: "testKind",
|
|
||||||
location: "testLocation",
|
|
||||||
name: "testName",
|
|
||||||
properties: {
|
properties: {
|
||||||
cassandraEndpoint: "https://testCassandraEndpoint.azure.com/",
|
cassandraEndpoint: "https://testCassandraEndpoint.azure.com/",
|
||||||
documentEndpoint: null,
|
|
||||||
gremlinEndpoint: null,
|
|
||||||
tableEndpoint: null,
|
|
||||||
},
|
},
|
||||||
type: "testType",
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createTerminal = (): NotebookTerminalComponent => {
|
const testNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||||
return new NotebookTerminalComponent({
|
authToken: "authToken",
|
||||||
notebookServerInfo: {
|
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com",
|
||||||
authToken: "testAuthToken",
|
|
||||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/",
|
|
||||||
},
|
|
||||||
databaseAccount: createTestDatabaseAccount(),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createMongo32Terminal = (): NotebookTerminalComponent => {
|
const testMongoNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||||
return new NotebookTerminalComponent({
|
authToken: "authToken",
|
||||||
notebookServerInfo: {
|
|
||||||
authToken: "testAuthToken",
|
|
||||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
|
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
|
||||||
},
|
|
||||||
databaseAccount: createTestMongo32DatabaseAccount(),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createMongo36Terminal = (): NotebookTerminalComponent => {
|
const testCassandraNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||||
return new NotebookTerminalComponent({
|
authToken: "authToken",
|
||||||
notebookServerInfo: {
|
|
||||||
authToken: "testAuthToken",
|
|
||||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
|
|
||||||
},
|
|
||||||
databaseAccount: createTestMongo36DatabaseAccount(),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const createCassandraTerminal = (): NotebookTerminalComponent => {
|
|
||||||
return new NotebookTerminalComponent({
|
|
||||||
notebookServerInfo: {
|
|
||||||
authToken: "testAuthToken",
|
|
||||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/cassandra",
|
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/cassandra",
|
||||||
},
|
|
||||||
databaseAccount: createTestCassandraDatabaseAccount(),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("NotebookTerminalComponent", () => {
|
describe("NotebookTerminalComponent", () => {
|
||||||
it("getTerminalParams: Test for terminal", () => {
|
it("renders terminal", () => {
|
||||||
const terminal: NotebookTerminalComponent = createTerminal();
|
const props: NotebookTerminalComponentProps = {
|
||||||
const params: Map<string, string> = terminal.getTerminalParams();
|
databaseAccount: testAccount,
|
||||||
|
notebookServerInfo: testNotebookServerInfo,
|
||||||
|
};
|
||||||
|
|
||||||
expect(params).toEqual(
|
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||||
new Map<string, string>([["terminal", "true"]])
|
expect(wrapper).toMatchSnapshot();
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("getTerminalParams: Test for Mongo 3.2 terminal", () => {
|
it("renders mongo 3.2 shell", () => {
|
||||||
const terminal: NotebookTerminalComponent = createMongo32Terminal();
|
const props: NotebookTerminalComponentProps = {
|
||||||
const params: Map<string, string> = terminal.getTerminalParams();
|
databaseAccount: testMongo32Account,
|
||||||
|
notebookServerInfo: testMongoNotebookServerInfo,
|
||||||
|
};
|
||||||
|
|
||||||
expect(params).toEqual(
|
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||||
new Map<string, string>([
|
expect(wrapper).toMatchSnapshot();
|
||||||
["terminal", "true"],
|
|
||||||
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.documentEndpoint).host],
|
|
||||||
])
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("getTerminalParams: Test for Mongo 3.6 terminal", () => {
|
it("renders mongo 3.6 shell", () => {
|
||||||
const terminal: NotebookTerminalComponent = createMongo36Terminal();
|
const props: NotebookTerminalComponentProps = {
|
||||||
const params: Map<string, string> = terminal.getTerminalParams();
|
databaseAccount: testMongo36Account,
|
||||||
|
notebookServerInfo: testMongoNotebookServerInfo,
|
||||||
|
};
|
||||||
|
|
||||||
expect(params).toEqual(
|
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||||
new Map<string, string>([
|
expect(wrapper).toMatchSnapshot();
|
||||||
["terminal", "true"],
|
|
||||||
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.mongoEndpoint).host],
|
|
||||||
])
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("getTerminalParams: Test for Cassandra terminal", () => {
|
it("renders cassandra shell", () => {
|
||||||
const terminal: NotebookTerminalComponent = createCassandraTerminal();
|
const props: NotebookTerminalComponentProps = {
|
||||||
const params: Map<string, string> = terminal.getTerminalParams();
|
databaseAccount: testCassandraAccount,
|
||||||
|
notebookServerInfo: testCassandraNotebookServerInfo,
|
||||||
|
};
|
||||||
|
|
||||||
expect(params).toEqual(
|
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||||
new Map<string, string>([
|
expect(wrapper).toMatchSnapshot();
|
||||||
["terminal", "true"],
|
|
||||||
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.cassandraEndpoint).host],
|
|
||||||
])
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
* Wrapper around Notebook server terminal
|
* Wrapper around Notebook server terminal
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import postRobot from "post-robot";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import * as StringUtils from "../../../Utils/StringUtils";
|
import { TerminalProps } from "../../../Terminal/TerminalProps";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { TerminalQueryParams } from "../../../Common/Constants";
|
import * as StringUtils from "../../../Utils/StringUtils";
|
||||||
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
|
||||||
|
|
||||||
export interface NotebookTerminalComponentProps {
|
export interface NotebookTerminalComponentProps {
|
||||||
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
||||||
@@ -15,79 +15,69 @@ export interface NotebookTerminalComponentProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class NotebookTerminalComponent extends React.Component<NotebookTerminalComponentProps> {
|
export class NotebookTerminalComponent extends React.Component<NotebookTerminalComponentProps> {
|
||||||
|
private terminalWindow: Window;
|
||||||
|
|
||||||
constructor(props: NotebookTerminalComponentProps) {
|
constructor(props: NotebookTerminalComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
this.sendPropsToTerminalFrame();
|
||||||
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className="notebookTerminalContainer">
|
<div className="notebookTerminalContainer">
|
||||||
<iframe
|
<iframe
|
||||||
title="Terminal to Notebook Server"
|
title="Terminal to Notebook Server"
|
||||||
src={NotebookTerminalComponent.createNotebookAppSrc(this.props.notebookServerInfo, this.getTerminalParams())}
|
onLoad={(event) => this.handleFrameLoad(event)}
|
||||||
|
src="terminal.html"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTerminalParams(): Map<string, string> {
|
handleFrameLoad(event: React.SyntheticEvent<HTMLIFrameElement, Event>): void {
|
||||||
let params: Map<string, string> = new Map<string, string>();
|
this.terminalWindow = (event.target as HTMLIFrameElement).contentWindow;
|
||||||
params.set(TerminalQueryParams.Terminal, "true");
|
this.sendPropsToTerminalFrame();
|
||||||
|
|
||||||
const terminalEndpoint: string = this.tryGetTerminalEndpoint();
|
|
||||||
if (terminalEndpoint) {
|
|
||||||
params.set(TerminalQueryParams.TerminalEndpoint, terminalEndpoint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return params;
|
sendPropsToTerminalFrame(): void {
|
||||||
|
if (!this.terminalWindow) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public tryGetTerminalEndpoint(): string | null {
|
const props: TerminalProps = {
|
||||||
let terminalEndpoint: string | null;
|
terminalEndpoint: this.tryGetTerminalEndpoint(),
|
||||||
|
notebookServerEndpoint: this.props.notebookServerInfo?.notebookServerEndpoint,
|
||||||
|
authToken: this.props.notebookServerInfo?.authToken,
|
||||||
|
subscriptionId: userContext.subscriptionId,
|
||||||
|
apiType: userContext.apiType,
|
||||||
|
authType: userContext.authType,
|
||||||
|
databaseAccount: userContext.databaseAccount,
|
||||||
|
};
|
||||||
|
|
||||||
const notebookServerEndpoint: string = this.props.notebookServerInfo.notebookServerEndpoint;
|
postRobot.send(this.terminalWindow, "props", props, {
|
||||||
|
domain: window.location.origin,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public tryGetTerminalEndpoint(): string | undefined {
|
||||||
|
let terminalEndpoint: string | undefined;
|
||||||
|
|
||||||
|
const notebookServerEndpoint = this.props.notebookServerInfo?.notebookServerEndpoint;
|
||||||
if (StringUtils.endsWith(notebookServerEndpoint, "mongo")) {
|
if (StringUtils.endsWith(notebookServerEndpoint, "mongo")) {
|
||||||
let mongoShellEndpoint: string = this.props.databaseAccount.properties.mongoEndpoint;
|
// mongoEndpoint is only available for Mongo 3.6 and higher, fallback to documentEndpoint otherwise
|
||||||
if (!mongoShellEndpoint) {
|
terminalEndpoint =
|
||||||
// mongoEndpoint is only available for Mongo 3.6 and higher.
|
this.props.databaseAccount?.properties.mongoEndpoint || this.props.databaseAccount?.properties.documentEndpoint;
|
||||||
// Fallback to documentEndpoint otherwise.
|
|
||||||
mongoShellEndpoint = this.props.databaseAccount.properties.documentEndpoint;
|
|
||||||
}
|
|
||||||
terminalEndpoint = mongoShellEndpoint;
|
|
||||||
} else if (StringUtils.endsWith(notebookServerEndpoint, "cassandra")) {
|
} else if (StringUtils.endsWith(notebookServerEndpoint, "cassandra")) {
|
||||||
terminalEndpoint = this.props.databaseAccount.properties.cassandraEndpoint;
|
terminalEndpoint = this.props.databaseAccount?.properties.cassandraEndpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (terminalEndpoint) {
|
if (terminalEndpoint) {
|
||||||
return new URL(terminalEndpoint).host;
|
return new URL(terminalEndpoint).host;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static createNotebookAppSrc(
|
return undefined;
|
||||||
serverInfo: DataModels.NotebookWorkspaceConnectionInfo,
|
|
||||||
params: Map<string, string>
|
|
||||||
): string {
|
|
||||||
if (!serverInfo.notebookServerEndpoint) {
|
|
||||||
handleError(
|
|
||||||
"Notebook server endpoint not defined. Terminal will fail to connect to jupyter server.",
|
|
||||||
"NotebookTerminalComponent/createNotebookAppSrc"
|
|
||||||
);
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
params.set(TerminalQueryParams.Server, serverInfo.notebookServerEndpoint);
|
|
||||||
if (serverInfo.authToken && serverInfo.authToken.length > 0) {
|
|
||||||
params.set(TerminalQueryParams.Token, serverInfo.authToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
params.set(TerminalQueryParams.SubscriptionId, userContext.subscriptionId);
|
|
||||||
|
|
||||||
let result: string = "terminal.html?";
|
|
||||||
for (let key of params.keys()) {
|
|
||||||
result += `${key}=${encodeURIComponent(params.get(key))}&`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`NotebookTerminalComponent renders cassandra shell 1`] = `
|
||||||
|
<div
|
||||||
|
className="notebookTerminalContainer"
|
||||||
|
>
|
||||||
|
<iframe
|
||||||
|
onLoad={[Function]}
|
||||||
|
src="terminal.html"
|
||||||
|
title="Terminal to Notebook Server"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`NotebookTerminalComponent renders mongo 3.2 shell 1`] = `
|
||||||
|
<div
|
||||||
|
className="notebookTerminalContainer"
|
||||||
|
>
|
||||||
|
<iframe
|
||||||
|
onLoad={[Function]}
|
||||||
|
src="terminal.html"
|
||||||
|
title="Terminal to Notebook Server"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`NotebookTerminalComponent renders mongo 3.6 shell 1`] = `
|
||||||
|
<div
|
||||||
|
className="notebookTerminalContainer"
|
||||||
|
>
|
||||||
|
<iframe
|
||||||
|
onLoad={[Function]}
|
||||||
|
src="terminal.html"
|
||||||
|
title="Terminal to Notebook Server"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`NotebookTerminalComponent renders terminal 1`] = `
|
||||||
|
<div
|
||||||
|
className="notebookTerminalContainer"
|
||||||
|
>
|
||||||
|
<iframe
|
||||||
|
onLoad={[Function]}
|
||||||
|
src="terminal.html"
|
||||||
|
title="Terminal to Notebook Server"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@@ -17,6 +17,8 @@ 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 { Dialog, TextFieldProps, useDialog } from "../Dialog";
|
import { Dialog, TextFieldProps, useDialog } from "../Dialog";
|
||||||
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
|
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
|
||||||
import "./NotebookViewerComponent.less";
|
import "./NotebookViewerComponent.less";
|
||||||
@@ -55,7 +57,7 @@ export class NotebookViewerComponent
|
|||||||
databaseAccountName: undefined,
|
databaseAccountName: undefined,
|
||||||
defaultExperience: "NotebookViewer",
|
defaultExperience: "NotebookViewer",
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
cellEditorType: "monaco",
|
cellEditorType: "codemirror",
|
||||||
autoSaveInterval: 365 * 24 * 3600 * 1000, // There is no way to turn off auto-save, set to 1 year
|
autoSaveInterval: 365 * 24 * 3600 * 1000, // There is no way to turn off auto-save, set to 1 year
|
||||||
contentProvider: contents.JupyterContentProvider, // NotebookViewer only knows how to talk to Jupyter contents API
|
contentProvider: contents.JupyterContentProvider, // NotebookViewer only knows how to talk to Jupyter contents API
|
||||||
});
|
});
|
||||||
@@ -146,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 my notebooks"}
|
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}
|
||||||
|
|||||||
@@ -29,8 +29,9 @@ import { QueriesClient } from "../../../Common/QueriesClient";
|
|||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { useDialog } from "../Dialog";
|
||||||
|
|
||||||
const title: string = "Open Saved Queries";
|
const title = "Open Saved Queries";
|
||||||
|
|
||||||
export interface QueriesGridComponentProps {
|
export interface QueriesGridComponentProps {
|
||||||
queriesClient: QueriesClient;
|
queriesClient: QueriesClient;
|
||||||
@@ -196,9 +197,9 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
|
|||||||
{
|
{
|
||||||
key: "Action",
|
key: "Action",
|
||||||
name: "Action",
|
name: "Action",
|
||||||
fieldName: null,
|
fieldName: undefined,
|
||||||
minWidth: 70,
|
minWidth: 70,
|
||||||
onRender: (query: Query, index: number, column: IColumn) => {
|
onRender: (query: Query) => {
|
||||||
const buttonProps: IButtonProps = {
|
const buttonProps: IButtonProps = {
|
||||||
iconProps: {
|
iconProps: {
|
||||||
iconName: "More",
|
iconName: "More",
|
||||||
@@ -214,19 +215,19 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
|
|||||||
{
|
{
|
||||||
key: "Open",
|
key: "Open",
|
||||||
text: "Open query",
|
text: "Open query",
|
||||||
onClick: (event: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>, menuItem: any) => {
|
onClick: () => {
|
||||||
this.props.onQuerySelect(query);
|
this.props.onQuerySelect(query);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "Delete",
|
key: "Delete",
|
||||||
text: "Delete query",
|
text: "Delete query",
|
||||||
onClick: async (
|
onClick: async () => {
|
||||||
event: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
|
useDialog.getState().showOkCancelModalDialog(
|
||||||
menuItem: any
|
"Confirm delete",
|
||||||
) => {
|
"Are you sure you want to delete this query?",
|
||||||
if (window.confirm("Are you sure you want to delete this query?")) {
|
"Delete",
|
||||||
const container = window.dataExplorer;
|
async () => {
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteSavedQuery, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteSavedQuery, {
|
||||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||||
paneTitle: title,
|
paneTitle: title,
|
||||||
@@ -254,7 +255,10 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
await this.fetchSavedQueries(); // get latest state
|
await this.fetchSavedQueries(); // get latest state
|
||||||
}
|
},
|
||||||
|
"Cancel",
|
||||||
|
undefined
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
@import "../../../../less/Common/Constants.less";
|
|
||||||
|
|
||||||
.radioSwitchComponent {
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
&>span:nth-child(n+2) {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.caption {
|
|
||||||
color: @BaseDark;
|
|
||||||
padding-left: @SmallSpace;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
/**
|
|
||||||
* Horizontal switch component
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Icon } from "@fluentui/react";
|
|
||||||
import * as React from "react";
|
|
||||||
import { NormalizedEventKey } from "../../../Common/Constants";
|
|
||||||
import "./RadioSwitchComponent.less";
|
|
||||||
|
|
||||||
export interface Choice {
|
|
||||||
key: string;
|
|
||||||
onSelect: () => void;
|
|
||||||
label: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RadioSwitchComponentProps {
|
|
||||||
choices: Choice[];
|
|
||||||
selectedKey: string;
|
|
||||||
onSelectionKeyChange?: (newValue: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class RadioSwitchComponent extends React.Component<RadioSwitchComponentProps> {
|
|
||||||
public render(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<div className="radioSwitchComponent">
|
|
||||||
{this.props.choices.map((choice: Choice) => (
|
|
||||||
<span
|
|
||||||
tabIndex={0}
|
|
||||||
key={choice.key}
|
|
||||||
onClick={() => this.onSelect(choice)}
|
|
||||||
onKeyPress={(event) => this.onKeyPress(event, choice)}
|
|
||||||
>
|
|
||||||
<Icon iconName={this.props.selectedKey === choice.key ? "RadioBtnOn" : "RadioBtnOff"} />
|
|
||||||
<span className="caption">{choice.label}</span>
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onSelect(choice: Choice): void {
|
|
||||||
this.props.onSelectionKeyChange && this.props.onSelectionKeyChange(choice.key);
|
|
||||||
choice.onSelect();
|
|
||||||
}
|
|
||||||
|
|
||||||
private onKeyPress(event: React.KeyboardEvent<HTMLSpanElement>, choice: Choice): void {
|
|
||||||
if (event.key === NormalizedEventKey.Enter || event.key === NormalizedEventKey.Space) {
|
|
||||||
this.onSelect(choice);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
/**
|
|
||||||
* Generic abstract React component that senses its dimensions.
|
|
||||||
* It updates its state and re-renders if dimensions change.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import * as ResizeSensor from "css-element-queries/src/ResizeSensor";
|
|
||||||
|
|
||||||
export abstract class ResizeSensorComponent<P, S> extends React.Component<P, S> {
|
|
||||||
private isSensing: boolean = false;
|
|
||||||
private resizeSensor: any;
|
|
||||||
|
|
||||||
public constructor(props: P) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract onDimensionsChanged(width: number, height: number): void;
|
|
||||||
protected abstract getSensorTarget(): HTMLElement;
|
|
||||||
|
|
||||||
public componentDidUpdate(): void {
|
|
||||||
if (this.isSensing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bar = this.getSensorTarget();
|
|
||||||
if (bar.clientWidth > 0 || bar.clientHeight > 0) {
|
|
||||||
const oldPosition = bar.style.position;
|
|
||||||
// TODO Find a better way to use constructor
|
|
||||||
this.resizeSensor = new (ResizeSensor as any)(bar, () => {
|
|
||||||
this.onDimensionsChanged(bar.clientWidth, bar.clientHeight);
|
|
||||||
});
|
|
||||||
this.isSensing = true;
|
|
||||||
|
|
||||||
// ResizeSensor.js sets position to 'relative' which makes the dropdown menu appear clipped.
|
|
||||||
// Undoing doesn't seem to affect resize sensing functionality.
|
|
||||||
bar.style.position = oldPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentWillUnmount(): void {
|
|
||||||
if (!!this.resizeSensor) {
|
|
||||||
this.resizeSensor.detach();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -38,8 +38,6 @@ describe("SettingsComponent", () => {
|
|||||||
title: "Scale & Settings",
|
title: "Scale & Settings",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
node: undefined,
|
node: undefined,
|
||||||
hashLocation: "settings",
|
|
||||||
onUpdateTabsButtons: undefined,
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -128,7 +126,6 @@ describe("SettingsComponent", () => {
|
|||||||
isDatabaseExpanded: undefined,
|
isDatabaseExpanded: undefined,
|
||||||
isDatabaseShared: ko.computed(() => true),
|
isDatabaseShared: ko.computed(() => true),
|
||||||
selectedSubnodeKind: undefined,
|
selectedSubnodeKind: undefined,
|
||||||
selectDatabase: undefined,
|
|
||||||
expandDatabase: undefined,
|
expandDatabase: undefined,
|
||||||
collapseDatabase: undefined,
|
collapseDatabase: undefined,
|
||||||
loadCollections: undefined,
|
loadCollections: undefined,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { userContext } from "../../../UserContext";
|
|||||||
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
|
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
|
||||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../../Explorer";
|
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
|
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
|
||||||
import "./SettingsComponent.less";
|
import "./SettingsComponent.less";
|
||||||
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
|
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
|
||||||
@@ -121,7 +121,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
private collection: ViewModels.Collection;
|
private collection: ViewModels.Collection;
|
||||||
private database: ViewModels.Database;
|
private database: ViewModels.Database;
|
||||||
private offer: DataModels.Offer;
|
private offer: DataModels.Offer;
|
||||||
private container: Explorer;
|
|
||||||
private changeFeedPolicyVisible: boolean;
|
private changeFeedPolicyVisible: boolean;
|
||||||
private isFixedContainer: boolean;
|
private isFixedContainer: boolean;
|
||||||
private shouldShowIndexingPolicyEditor: boolean;
|
private shouldShowIndexingPolicyEditor: boolean;
|
||||||
@@ -133,7 +132,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.isCollectionSettingsTab = this.props.settingsTab.tabKind === ViewModels.CollectionTabKind.CollectionSettingsV2;
|
this.isCollectionSettingsTab = this.props.settingsTab.tabKind === ViewModels.CollectionTabKind.CollectionSettingsV2;
|
||||||
if (this.isCollectionSettingsTab) {
|
if (this.isCollectionSettingsTab) {
|
||||||
this.collection = this.props.settingsTab.collection as ViewModels.Collection;
|
this.collection = this.props.settingsTab.collection as ViewModels.Collection;
|
||||||
this.container = this.collection?.container;
|
|
||||||
this.offer = this.collection?.offer();
|
this.offer = this.collection?.offer();
|
||||||
this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl();
|
this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl();
|
||||||
this.shouldShowIndexingPolicyEditor = userContext.apiType !== "Cassandra" && userContext.apiType !== "Mongo";
|
this.shouldShowIndexingPolicyEditor = userContext.apiType !== "Cassandra" && userContext.apiType !== "Mongo";
|
||||||
@@ -145,7 +143,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
userContext.apiType === "Mongo" && (!this.collection?.partitionKey || this.collection?.partitionKey.systemKey);
|
userContext.apiType === "Mongo" && (!this.collection?.partitionKey || this.collection?.partitionKey.systemKey);
|
||||||
} else {
|
} else {
|
||||||
this.database = this.props.settingsTab.database;
|
this.database = this.props.settingsTab.database;
|
||||||
this.container = this.database?.container;
|
|
||||||
this.offer = this.database?.offer();
|
this.offer = this.database?.offer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,13 +219,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.setAutoPilotStates();
|
this.setAutoPilotStates();
|
||||||
this.setBaseline();
|
this.setBaseline();
|
||||||
if (this.props.settingsTab.isActive()) {
|
if (this.props.settingsTab.isActive()) {
|
||||||
this.props.settingsTab.getContainer().onUpdateTabsButtons(this.getTabsButtons());
|
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(): void {
|
componentDidUpdate(): void {
|
||||||
if (this.props.settingsTab.isActive()) {
|
if (this.props.settingsTab.isActive()) {
|
||||||
this.props.settingsTab.getContainer().onUpdateTabsButtons(this.getTabsButtons());
|
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +290,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.state.wasAutopilotOriginallySet !== this.state.isAutoPilotSelected;
|
this.state.wasAutopilotOriginallySet !== this.state.isAutoPilotSelected;
|
||||||
|
|
||||||
public shouldShowKeyspaceSharedThroughputMessage = (): boolean =>
|
public shouldShowKeyspaceSharedThroughputMessage = (): boolean =>
|
||||||
this.container && userContext.apiType === "Cassandra" && hasDatabaseSharedThroughput(this.collection);
|
userContext.apiType === "Cassandra" && hasDatabaseSharedThroughput(this.collection);
|
||||||
|
|
||||||
public hasConflictResolution = (): boolean =>
|
public hasConflictResolution = (): boolean =>
|
||||||
userContext?.databaseAccount?.properties?.enableMultipleWriteLocations &&
|
userContext?.databaseAccount?.properties?.enableMultipleWriteLocations &&
|
||||||
@@ -883,7 +880,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
const scaleComponentProps: ScaleComponentProps = {
|
const scaleComponentProps: ScaleComponentProps = {
|
||||||
collection: this.collection,
|
collection: this.collection,
|
||||||
database: this.database,
|
database: this.database,
|
||||||
container: this.container,
|
|
||||||
isFixedContainer: this.isFixedContainer,
|
isFixedContainer: this.isFixedContainer,
|
||||||
onThroughputChange: this.onThroughputChange,
|
onThroughputChange: this.onThroughputChange,
|
||||||
throughput: this.state.throughput,
|
throughput: this.state.throughput,
|
||||||
@@ -911,7 +907,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
|
|
||||||
const subSettingsComponentProps: SubSettingsComponentProps = {
|
const subSettingsComponentProps: SubSettingsComponentProps = {
|
||||||
collection: this.collection,
|
collection: this.collection,
|
||||||
container: this.container,
|
|
||||||
isAnalyticalStorageEnabled: this.isAnalyticalStorageEnabled,
|
isAnalyticalStorageEnabled: this.isAnalyticalStorageEnabled,
|
||||||
changeFeedPolicyVisible: this.changeFeedPolicyVisible,
|
changeFeedPolicyVisible: this.changeFeedPolicyVisible,
|
||||||
timeToLive: this.state.timeToLive,
|
timeToLive: this.state.timeToLive,
|
||||||
@@ -964,7 +959,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
|
|
||||||
const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
|
const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
|
||||||
collection: this.collection,
|
collection: this.collection,
|
||||||
container: this.container,
|
|
||||||
conflictResolutionPolicyMode: this.state.conflictResolutionPolicyMode,
|
conflictResolutionPolicyMode: this.state.conflictResolutionPolicyMode,
|
||||||
conflictResolutionPolicyModeBaseline: this.state.conflictResolutionPolicyModeBaseline,
|
conflictResolutionPolicyModeBaseline: this.state.conflictResolutionPolicyModeBaseline,
|
||||||
onConflictResolutionPolicyModeChange: this.onConflictResolutionPolicyModeChange,
|
onConflictResolutionPolicyModeChange: this.onConflictResolutionPolicyModeChange,
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import ko from "knockout";
|
|
||||||
import * as React from "react";
|
|
||||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
|
||||||
import { SettingsComponent, SettingsComponentProps } from "./SettingsComponent";
|
|
||||||
|
|
||||||
export class SettingsComponentAdapter implements ReactAdapter {
|
|
||||||
public parameters: ko.Computed<boolean>;
|
|
||||||
|
|
||||||
constructor(private props: SettingsComponentProps) {}
|
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
|
||||||
return this.parameters() ? <SettingsComponent {...this.props} /> : <></>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +1,45 @@
|
|||||||
import * as React from "react";
|
|
||||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
|
||||||
import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants";
|
|
||||||
import { Urls, StyleConstants } from "../../../Common/Constants";
|
|
||||||
import {
|
import {
|
||||||
getPriceCurrency,
|
DetailsList,
|
||||||
getCurrencySign,
|
DetailsListLayoutMode,
|
||||||
getAutoscalePricePerRu,
|
DetailsRow,
|
||||||
getMultimasterMultiplier,
|
|
||||||
computeRUUsagePriceHourly,
|
|
||||||
getPricePerRu,
|
|
||||||
estimatedCostDisclaimer,
|
|
||||||
} from "../../../Utils/PricingUtils";
|
|
||||||
import {
|
|
||||||
ITextFieldStyles,
|
|
||||||
ICheckboxStyles,
|
ICheckboxStyles,
|
||||||
IStackProps,
|
|
||||||
IStackTokens,
|
|
||||||
IChoiceGroupStyles,
|
IChoiceGroupStyles,
|
||||||
Link,
|
IColumn,
|
||||||
Text,
|
IDetailsColumnStyles,
|
||||||
IMessageBarStyles,
|
|
||||||
ITextStyles,
|
|
||||||
IDetailsRowStyles,
|
|
||||||
IStackStyles,
|
|
||||||
IDetailsListStyles,
|
IDetailsListStyles,
|
||||||
|
IDetailsRowProps,
|
||||||
|
IDetailsRowStyles,
|
||||||
IDropdownStyles,
|
IDropdownStyles,
|
||||||
|
IMessageBarStyles,
|
||||||
ISeparatorStyles,
|
ISeparatorStyles,
|
||||||
|
IStackProps,
|
||||||
|
IStackStyles,
|
||||||
|
IStackTokens,
|
||||||
|
ITextFieldStyles,
|
||||||
|
ITextStyles,
|
||||||
|
Link,
|
||||||
MessageBar,
|
MessageBar,
|
||||||
MessageBarType,
|
MessageBarType,
|
||||||
Stack,
|
SelectionMode,
|
||||||
Spinner,
|
Spinner,
|
||||||
SpinnerSize,
|
SpinnerSize,
|
||||||
DetailsList,
|
Stack,
|
||||||
IColumn,
|
Text,
|
||||||
SelectionMode,
|
|
||||||
DetailsListLayoutMode,
|
|
||||||
IDetailsRowProps,
|
|
||||||
DetailsRow,
|
|
||||||
IDetailsColumnStyles,
|
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import { isDirtyTypes, isDirty } from "./SettingsUtils";
|
import * as React from "react";
|
||||||
|
import { StyleConstants, Urls } from "../../../Common/Constants";
|
||||||
|
import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants";
|
||||||
|
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||||
|
import {
|
||||||
|
computeRUUsagePriceHourly,
|
||||||
|
estimatedCostDisclaimer,
|
||||||
|
getAutoscalePricePerRu,
|
||||||
|
getCurrencySign,
|
||||||
|
getMultimasterMultiplier,
|
||||||
|
getPriceCurrency,
|
||||||
|
getPricePerRu,
|
||||||
|
} from "../../../Utils/PricingUtils";
|
||||||
|
import { isDirty, isDirtyTypes } from "./SettingsUtils";
|
||||||
|
|
||||||
export interface EstimatedSpendingDisplayProps {
|
export interface EstimatedSpendingDisplayProps {
|
||||||
costType: JSX.Element;
|
costType: JSX.Element;
|
||||||
@@ -65,7 +65,7 @@ export interface PriceBreakdown {
|
|||||||
currencySign: string;
|
currencySign: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14 } };
|
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14, color: "windowtext" } };
|
||||||
|
|
||||||
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
||||||
label: {
|
label: {
|
||||||
@@ -223,14 +223,15 @@ export const getRuPriceBreakdown = (
|
|||||||
multimasterEnabled: isMultimaster,
|
multimasterEnabled: isMultimaster,
|
||||||
isAutoscale: isAutoscale,
|
isAutoscale: isAutoscale,
|
||||||
});
|
});
|
||||||
const basePricePerRu: number = isAutoscale
|
const multimasterMultiplier = getMultimasterMultiplier(numberOfRegions, isMultimaster);
|
||||||
? getAutoscalePricePerRu(serverId, getMultimasterMultiplier(numberOfRegions, isMultimaster))
|
const pricePerRu: number = isAutoscale
|
||||||
: getPricePerRu(serverId);
|
? getAutoscalePricePerRu(serverId, multimasterMultiplier)
|
||||||
|
: getPricePerRu(serverId, multimasterMultiplier);
|
||||||
return {
|
return {
|
||||||
hourlyPrice: hourlyPrice,
|
hourlyPrice,
|
||||||
dailyPrice: hourlyPrice * 24,
|
dailyPrice: hourlyPrice * 24,
|
||||||
monthlyPrice: hourlyPrice * hoursInAMonth,
|
monthlyPrice: hourlyPrice * hoursInAMonth,
|
||||||
pricePerRu: basePricePerRu * getMultimasterMultiplier(numberOfRegions, isMultimaster),
|
pricePerRu,
|
||||||
currency: getPriceCurrency(serverId),
|
currency: getPriceCurrency(serverId),
|
||||||
currencySign: getCurrencySign(serverId),
|
currencySign: getCurrencySign(serverId),
|
||||||
};
|
};
|
||||||
@@ -271,7 +272,7 @@ export const manualToAutoscaleDisclaimerElement: JSX.Element = (
|
|||||||
<Text styles={infoAndToolTipTextStyle} id="manualToAutoscaleDisclaimerElement">
|
<Text styles={infoAndToolTipTextStyle} id="manualToAutoscaleDisclaimerElement">
|
||||||
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings
|
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings
|
||||||
and storage of your resource. After autoscale has been enabled, you can change the max RU/s.{" "}
|
and storage of your resource. After autoscale has been enabled, you can change the max RU/s.{" "}
|
||||||
<a href={Urls.autoscaleMigration}>Learn more</a>
|
<Link href={Urls.autoscaleMigration}>Learn more</Link>
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { ConflictResolutionComponentProps, ConflictResolutionComponent } from "./ConflictResolutionComponent";
|
|
||||||
import { container, collection } from "../TestUtils";
|
|
||||||
import * as DataModels from "../../../../Contracts/DataModels";
|
import * as DataModels from "../../../../Contracts/DataModels";
|
||||||
|
import { collection } from "../TestUtils";
|
||||||
|
import { ConflictResolutionComponent, ConflictResolutionComponentProps } from "./ConflictResolutionComponent";
|
||||||
|
|
||||||
describe("ConflictResolutionComponent", () => {
|
describe("ConflictResolutionComponent", () => {
|
||||||
const baseProps: ConflictResolutionComponentProps = {
|
const baseProps: ConflictResolutionComponentProps = {
|
||||||
collection: collection,
|
collection: collection,
|
||||||
container: container,
|
|
||||||
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode.Custom,
|
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode.Custom,
|
||||||
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode.Custom,
|
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode.Custom,
|
||||||
onConflictResolutionPolicyModeChange: () => {
|
onConflictResolutionPolicyModeChange: () => {
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
|
import { ChoiceGroup, IChoiceGroupOption, ITextFieldProps, Stack, TextField } from "@fluentui/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ViewModels from "../../../../Contracts/ViewModels";
|
|
||||||
import * as DataModels from "../../../../Contracts/DataModels";
|
import * as DataModels from "../../../../Contracts/DataModels";
|
||||||
import Explorer from "../../../Explorer";
|
import * as ViewModels from "../../../../Contracts/ViewModels";
|
||||||
import {
|
import {
|
||||||
getTextFieldStyles,
|
|
||||||
conflictResolutionLwwTooltip,
|
|
||||||
conflictResolutionCustomToolTip,
|
conflictResolutionCustomToolTip,
|
||||||
subComponentStackProps,
|
conflictResolutionLwwTooltip,
|
||||||
getChoiceGroupStyles,
|
getChoiceGroupStyles,
|
||||||
|
getTextFieldStyles,
|
||||||
|
subComponentStackProps,
|
||||||
} from "../SettingsRenderUtils";
|
} from "../SettingsRenderUtils";
|
||||||
import { TextField, ITextFieldProps, Stack, IChoiceGroupOption, ChoiceGroup } from "@fluentui/react";
|
|
||||||
import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
|
|
||||||
import { isDirty } from "../SettingsUtils";
|
import { isDirty } from "../SettingsUtils";
|
||||||
|
import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
|
||||||
|
|
||||||
export interface ConflictResolutionComponentProps {
|
export interface ConflictResolutionComponentProps {
|
||||||
collection: ViewModels.Collection;
|
collection: ViewModels.Collection;
|
||||||
container: Explorer;
|
|
||||||
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
||||||
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
|
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
|
||||||
onConflictResolutionPolicyModeChange: (newMode: DataModels.ConflictResolutionMode) => void;
|
onConflictResolutionPolicyModeChange: (newMode: DataModels.ConflictResolutionMode) => void;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ exports[`IndexingPolicyRefreshComponent renders 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,20 +7,17 @@ import * as SharedConstants from "../../../../Shared/Constants";
|
|||||||
import { updateUserContext } from "../../../../UserContext";
|
import { updateUserContext } from "../../../../UserContext";
|
||||||
import Explorer from "../../../Explorer";
|
import Explorer from "../../../Explorer";
|
||||||
import { throughputUnit } from "../SettingsRenderUtils";
|
import { throughputUnit } from "../SettingsRenderUtils";
|
||||||
import { collection, container } from "../TestUtils";
|
import { collection } from "../TestUtils";
|
||||||
import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent";
|
import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent";
|
||||||
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
|
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
|
||||||
|
|
||||||
describe("ScaleComponent", () => {
|
describe("ScaleComponent", () => {
|
||||||
const nonNationalCloudContainer = new Explorer();
|
const nonNationalCloudContainer = new Explorer();
|
||||||
nonNationalCloudContainer.isRunningOnNationalCloud = () => false;
|
|
||||||
|
|
||||||
const targetThroughput = 6000;
|
const targetThroughput = 6000;
|
||||||
|
|
||||||
const baseProps: ScaleComponentProps = {
|
const baseProps: ScaleComponentProps = {
|
||||||
collection: collection,
|
collection: collection,
|
||||||
database: undefined,
|
database: undefined,
|
||||||
container: container,
|
|
||||||
isFixedContainer: false,
|
isFixedContainer: false,
|
||||||
onThroughputChange: () => {
|
onThroughputChange: () => {
|
||||||
return;
|
return;
|
||||||
@@ -111,7 +108,7 @@ describe("ScaleComponent", () => {
|
|||||||
let scaleComponent = new ScaleComponent(baseProps);
|
let scaleComponent = new ScaleComponent(baseProps);
|
||||||
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)");
|
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)");
|
||||||
|
|
||||||
let newProps = { ...baseProps, container: nonNationalCloudContainer };
|
let newProps = { ...baseProps };
|
||||||
scaleComponent = new ScaleComponent(newProps);
|
scaleComponent = new ScaleComponent(newProps);
|
||||||
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)");
|
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)");
|
||||||
|
|
||||||
@@ -124,7 +121,7 @@ describe("ScaleComponent", () => {
|
|||||||
let scaleComponent = new ScaleComponent(baseProps);
|
let scaleComponent = new ScaleComponent(baseProps);
|
||||||
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true);
|
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true);
|
||||||
|
|
||||||
const newProps = { ...baseProps, container: nonNationalCloudContainer };
|
const newProps = { ...baseProps };
|
||||||
scaleComponent = new ScaleComponent(newProps);
|
scaleComponent = new ScaleComponent(newProps);
|
||||||
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true);
|
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as ViewModels from "../../../../Contracts/ViewModels";
|
|||||||
import * as SharedConstants from "../../../../Shared/Constants";
|
import * as SharedConstants from "../../../../Shared/Constants";
|
||||||
import { userContext } from "../../../../UserContext";
|
import { userContext } from "../../../../UserContext";
|
||||||
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
|
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
|
||||||
import Explorer from "../../../Explorer";
|
import { isRunningOnNationalCloud } from "../../../../Utils/CloudUtils";
|
||||||
import {
|
import {
|
||||||
getTextFieldStyles,
|
getTextFieldStyles,
|
||||||
getThroughputApplyLongDelayMessage,
|
getThroughputApplyLongDelayMessage,
|
||||||
@@ -23,7 +23,6 @@ import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents
|
|||||||
export interface ScaleComponentProps {
|
export interface ScaleComponentProps {
|
||||||
collection: ViewModels.Collection;
|
collection: ViewModels.Collection;
|
||||||
database: ViewModels.Database;
|
database: ViewModels.Database;
|
||||||
container: Explorer;
|
|
||||||
isFixedContainer: boolean;
|
isFixedContainer: boolean;
|
||||||
onThroughputChange: (newThroughput: number) => void;
|
onThroughputChange: (newThroughput: number) => void;
|
||||||
throughput: number;
|
throughput: number;
|
||||||
@@ -109,11 +108,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public canThroughputExceedMaximumValue = (): boolean => {
|
public canThroughputExceedMaximumValue = (): boolean => {
|
||||||
return (
|
return !this.props.isFixedContainer && configContext.platform === Platform.Portal && !isRunningOnNationalCloud();
|
||||||
!this.props.isFixedContainer &&
|
|
||||||
configContext.platform === Platform.Portal &&
|
|
||||||
!this.props.container.isRunningOnNationalCloud()
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public getInitialNotificationElement = (): JSX.Element => {
|
public getInitialNotificationElement = (): JSX.Element => {
|
||||||
@@ -202,10 +197,12 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getFreeTierInfoMessage(): JSX.Element {
|
private getFreeTierInfoMessage(): JSX.Element {
|
||||||
|
const freeTierLimits = SharedConstants.FreeTierLimits;
|
||||||
return (
|
return (
|
||||||
<Text>
|
<Text>
|
||||||
With free tier, you will get the first 400 RU/s and 5 GB of storage in this account for free. To keep your
|
With free tier, you will get the first {freeTierLimits.RU} RU/s and {freeTierLimits.Storage} GB of storage in
|
||||||
account free, keep the total RU/s across all resources in the account to 400 RU/s.
|
this account for free. To keep your account free, keep the total RU/s across all resources in the account to{" "}
|
||||||
|
{freeTierLimits.RU} RU/s.
|
||||||
<Link
|
<Link
|
||||||
href="https://docs.microsoft.com/en-us/azure/cosmos-db/understand-your-bill#billing-examples-with-free-tier-accounts"
|
href="https://docs.microsoft.com/en-us/azure/cosmos-db/understand-your-bill#billing-examples-with-free-tier-accounts"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|||||||
@@ -4,14 +4,12 @@ import { DatabaseAccount } from "../../../../Contracts/DataModels";
|
|||||||
import { updateUserContext } from "../../../../UserContext";
|
import { updateUserContext } from "../../../../UserContext";
|
||||||
import Explorer from "../../../Explorer";
|
import Explorer from "../../../Explorer";
|
||||||
import { ChangeFeedPolicyState, GeospatialConfigType, TtlOff, TtlOn, TtlOnNoDefault, TtlType } from "../SettingsUtils";
|
import { ChangeFeedPolicyState, GeospatialConfigType, TtlOff, TtlOn, TtlOnNoDefault, TtlType } from "../SettingsUtils";
|
||||||
import { collection, container } from "../TestUtils";
|
import { collection } from "../TestUtils";
|
||||||
import { SubSettingsComponent, SubSettingsComponentProps } from "./SubSettingsComponent";
|
import { SubSettingsComponent, SubSettingsComponentProps } from "./SubSettingsComponent";
|
||||||
|
|
||||||
describe("SubSettingsComponent", () => {
|
describe("SubSettingsComponent", () => {
|
||||||
const baseProps: SubSettingsComponentProps = {
|
const baseProps: SubSettingsComponentProps = {
|
||||||
collection: collection,
|
collection: collection,
|
||||||
container: container,
|
|
||||||
|
|
||||||
timeToLive: TtlType.On,
|
timeToLive: TtlType.On,
|
||||||
timeToLiveBaseline: TtlType.On,
|
timeToLiveBaseline: TtlType.On,
|
||||||
onTtlChange: () => {
|
onTtlChange: () => {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { ChoiceGroup, IChoiceGroupOption, Label, Link, MessageBar, Stack, Text,
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ViewModels from "../../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../../Contracts/ViewModels";
|
||||||
import { userContext } from "../../../../UserContext";
|
import { userContext } from "../../../../UserContext";
|
||||||
import Explorer from "../../../Explorer";
|
|
||||||
import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
||||||
import {
|
import {
|
||||||
changeFeedPolicyToolTip,
|
changeFeedPolicyToolTip,
|
||||||
@@ -28,8 +27,6 @@ import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
|
|||||||
|
|
||||||
export interface SubSettingsComponentProps {
|
export interface SubSettingsComponentProps {
|
||||||
collection: ViewModels.Collection;
|
collection: ViewModels.Collection;
|
||||||
container: Explorer;
|
|
||||||
|
|
||||||
timeToLive: TtlType;
|
timeToLive: TtlType;
|
||||||
timeToLiveBaseline: TtlType;
|
timeToLiveBaseline: TtlType;
|
||||||
|
|
||||||
|
|||||||
@@ -155,7 +155,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
this.state = {
|
this.state = {
|
||||||
spendAckChecked: this.props.spendAckChecked,
|
spendAckChecked: this.props.spendAckChecked,
|
||||||
exceedFreeTierThroughput:
|
exceedFreeTierThroughput:
|
||||||
this.props.isFreeTierAccount && !this.props.isAutoPilotSelected && this.props.throughput > 400,
|
this.props.isFreeTierAccount &&
|
||||||
|
!this.props.isAutoPilotSelected &&
|
||||||
|
this.props.throughput > SharedConstants.FreeTierLimits.RU,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep;
|
this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep;
|
||||||
@@ -441,7 +443,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
if (this.overrideWithAutoPilotSettings()) {
|
if (this.overrideWithAutoPilotSettings()) {
|
||||||
this.props.onMaxAutoPilotThroughputChange(newThroughput);
|
this.props.onMaxAutoPilotThroughputChange(newThroughput);
|
||||||
} else {
|
} else {
|
||||||
this.setState({ exceedFreeTierThroughput: this.props.isFreeTierAccount && newThroughput > 400 });
|
this.setState({
|
||||||
|
exceedFreeTierThroughput: this.props.isFreeTierAccount && newThroughput > SharedConstants.FreeTierLimits.RU,
|
||||||
|
});
|
||||||
this.props.onThroughputChange(newThroughput);
|
this.props.onThroughputChange(newThroughput);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -581,9 +585,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}
|
messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}
|
||||||
styles={messageBarStyles}
|
styles={messageBarStyles}
|
||||||
>
|
>
|
||||||
{
|
{`Billing will apply if you provision more than ${SharedConstants.FreeTierLimits.RU} RU/s of manual throughput, or if the resource scales beyond ${SharedConstants.FreeTierLimits.RU} RU/s with autoscale.`}
|
||||||
"Billing will apply if you provision more than 400 RU/s of manual throughput, or if the resource scales beyond 400 RU/s with autoscale."
|
|
||||||
}
|
|
||||||
</MessageBar>
|
</MessageBar>
|
||||||
)}
|
)}
|
||||||
{this.props.getThroughputWarningMessage() && (
|
{this.props.getThroughputWarningMessage() && (
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -39,6 +40,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -73,6 +75,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -80,11 +83,11 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
|||||||
>
|
>
|
||||||
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s.
|
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s.
|
||||||
|
|
||||||
<a
|
<StyledLinkBase
|
||||||
href="https://aka.ms/cosmos-autoscale-migration"
|
href="https://aka.ms/cosmos-autoscale-migration"
|
||||||
>
|
>
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</StyledLinkBase>
|
||||||
</Text>
|
</Text>
|
||||||
</StyledMessageBar>
|
</StyledMessageBar>
|
||||||
<StyledChoiceGroup
|
<StyledChoiceGroup
|
||||||
@@ -186,6 +189,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -460,6 +464,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ exports[`ScaleComponent renders with correct initial notification 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -412,6 +413,7 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -952,6 +954,7 @@ exports[`SubSettingsComponent renders 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1228,6 +1231,7 @@ exports[`SubSettingsComponent timeToLiveSeconds hidden 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ describe("SettingsUtils", () => {
|
|||||||
isDatabaseExpanded: ko.observable(false),
|
isDatabaseExpanded: ko.observable(false),
|
||||||
isDatabaseShared: ko.computed(() => true),
|
isDatabaseShared: ko.computed(() => true),
|
||||||
selectedSubnodeKind: ko.observable(undefined),
|
selectedSubnodeKind: ko.observable(undefined),
|
||||||
selectDatabase: undefined,
|
|
||||||
expandDatabase: undefined,
|
expandDatabase: undefined,
|
||||||
collapseDatabase: undefined,
|
collapseDatabase: undefined,
|
||||||
loadCollections: undefined,
|
loadCollections: undefined,
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -166,16 +167,17 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
>
|
>
|
||||||
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s.
|
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s.
|
||||||
|
|
||||||
<a
|
<StyledLinkBase
|
||||||
href="https://aka.ms/cosmos-autoscale-migration"
|
href="https://aka.ms/cosmos-autoscale-migration"
|
||||||
>
|
>
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</StyledLinkBase>
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -195,6 +197,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -207,6 +210,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -219,6 +223,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -230,6 +235,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -249,6 +255,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -264,6 +271,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -278,6 +286,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -291,6 +300,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -302,6 +312,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -321,6 +332,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -363,6 +375,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -378,6 +391,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -394,6 +408,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"color": "windowtext",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
@import "../../../../less/Common/Constants";
|
@import "../../../../less/Common/Constants";
|
||||||
|
|
||||||
.tabComponentContainer {
|
.tabComponentContainer {
|
||||||
overflow: hidden;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
.flex-display();
|
.flex-display();
|
||||||
.flex-direction();
|
.flex-direction();
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export class TabComponent extends React.Component<TabComponentProps> {
|
|||||||
as="span"
|
as="span"
|
||||||
className={className}
|
className={className}
|
||||||
role="presentation"
|
role="presentation"
|
||||||
onActivated={(e) => this.setActiveTab(index)}
|
onActivated={() => this.setActiveTab(index)}
|
||||||
aria-label={`Select tab: ${tab.title}`}
|
aria-label={`Select tab: ${tab.title}`}
|
||||||
>
|
>
|
||||||
{tab.title}
|
{tab.title}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { userContext } from "../../../../UserContext";
|
|||||||
import {
|
import {
|
||||||
calculateEstimateNumber,
|
calculateEstimateNumber,
|
||||||
computeRUUsagePriceHourly,
|
computeRUUsagePriceHourly,
|
||||||
|
estimatedCostDisclaimer,
|
||||||
getAutoscalePricePerRu,
|
getAutoscalePricePerRu,
|
||||||
getCurrencySign,
|
getCurrencySign,
|
||||||
getMultimasterMultiplier,
|
getMultimasterMultiplier,
|
||||||
@@ -42,11 +43,9 @@ export const CostEstimateText: FunctionComponent<CostEstimateTextProps> = ({
|
|||||||
const currency: string = getPriceCurrency(serverId);
|
const currency: string = getPriceCurrency(serverId);
|
||||||
const currencySign: string = getCurrencySign(serverId);
|
const currencySign: string = getCurrencySign(serverId);
|
||||||
const multiplier = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
const multiplier = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
||||||
const pricePerRu = isAutoscale
|
const pricePerRu = isAutoscale ? getAutoscalePricePerRu(serverId, multiplier) : getPricePerRu(serverId, multiplier);
|
||||||
? getAutoscalePricePerRu(serverId, multiplier) * multiplier
|
|
||||||
: getPricePerRu(serverId) * multiplier;
|
|
||||||
|
|
||||||
const iconWithEstimatedCostDisclaimer: JSX.Element = <InfoTooltip>PricingUtils.estimatedCostDisclaimer</InfoTooltip>;
|
const iconWithEstimatedCostDisclaimer: JSX.Element = <InfoTooltip>{estimatedCostDisclaimer}</InfoTooltip>;
|
||||||
|
|
||||||
if (isAutoscale) {
|
if (isAutoscale) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { ThroughputInput } from "./ThroughputInput";
|
|||||||
const props = {
|
const props = {
|
||||||
isDatabase: false,
|
isDatabase: false,
|
||||||
showFreeTierExceedThroughputTooltip: true,
|
showFreeTierExceedThroughputTooltip: true,
|
||||||
isSharded: false,
|
isSharded: true,
|
||||||
setThroughputValue: () => jest.fn(),
|
setThroughputValue: () => jest.fn(),
|
||||||
setIsAutoscale: () => jest.fn(),
|
setIsAutoscale: () => jest.fn(),
|
||||||
onCostAcknowledgeChange: () => jest.fn(),
|
onCostAcknowledgeChange: () => jest.fn(),
|
||||||
|
|||||||
@@ -41,9 +41,16 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
throughputHeaderText = AutoPilotUtils.getAutoPilotHeaderText().toLocaleLowerCase();
|
throughputHeaderText = AutoPilotUtils.getAutoPilotHeaderText().toLocaleLowerCase();
|
||||||
} else {
|
} else {
|
||||||
const minRU: string = SharedConstants.CollectionCreation.DefaultCollectionRUs400.toLocaleString();
|
const minRU: string = SharedConstants.CollectionCreation.DefaultCollectionRUs400.toLocaleString();
|
||||||
const maxRU: string = userContext.isTryCosmosDBSubscription
|
|
||||||
? Constants.TryCosmosExperience.maxRU.toLocaleString()
|
let maxRU: string;
|
||||||
: "unlimited";
|
if (userContext.isTryCosmosDBSubscription) {
|
||||||
|
maxRU = Constants.TryCosmosExperience.maxRU.toLocaleString();
|
||||||
|
} else if (!isSharded) {
|
||||||
|
maxRU = "10000";
|
||||||
|
} else {
|
||||||
|
maxRU = "unlimited";
|
||||||
|
}
|
||||||
|
|
||||||
throughputHeaderText = `throughput (${minRU} - ${maxRU} RU/s)`;
|
throughputHeaderText = `throughput (${minRU} - ${maxRU} RU/s)`;
|
||||||
}
|
}
|
||||||
return `${isDatabase ? "Database" : getCollectionName()} ${throughputHeaderText}`;
|
return `${isDatabase ? "Database" : getCollectionName()} ${throughputHeaderText}`;
|
||||||
@@ -111,6 +118,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
<input
|
<input
|
||||||
className="throughputInputRadioBtn"
|
className="throughputInputRadioBtn"
|
||||||
aria-label="Autoscale mode"
|
aria-label="Autoscale mode"
|
||||||
|
aria-required={true}
|
||||||
checked={isAutoscaleSelected}
|
checked={isAutoscaleSelected}
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
role="radio"
|
||||||
@@ -124,6 +132,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
aria-label="Manual mode"
|
aria-label="Manual mode"
|
||||||
checked={!isAutoscaleSelected}
|
checked={!isAutoscaleSelected}
|
||||||
type="radio"
|
type="radio"
|
||||||
|
aria-required={true}
|
||||||
role="radio"
|
role="radio"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={(e) => handleOnChangeMode(e, "Manual")}
|
onChange={(e) => handleOnChangeMode(e, "Manual")}
|
||||||
@@ -187,9 +196,8 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
<TooltipHost
|
<TooltipHost
|
||||||
directionalHint={DirectionalHint.topLeftEdge}
|
directionalHint={DirectionalHint.topLeftEdge}
|
||||||
content={
|
content={
|
||||||
showFreeTierExceedThroughputTooltip &&
|
showFreeTierExceedThroughputTooltip && throughput > SharedConstants.FreeTierLimits.RU
|
||||||
throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs400
|
? `The first ${SharedConstants.FreeTierLimits.RU} RU/s in this account are free. Billing will apply to any throughput beyond ${SharedConstants.FreeTierLimits.RU} RU/s.`
|
||||||
? "The first 400 RU/s in this account are free. Billing will apply to any throughput beyond 400 RU/s."
|
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,308 +0,0 @@
|
|||||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
|
||||||
import * as ko from "knockout";
|
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
|
||||||
import ThroughputInputComponentAutoscaleV3 from "./ThroughputInputComponentAutoscaleV3.html";
|
|
||||||
import { KeyCodes } from "../../../Common/Constants";
|
|
||||||
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
|
|
||||||
|
|
||||||
import { userContext } from "../../../UserContext";
|
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
/**
|
|
||||||
* Throughput Input:
|
|
||||||
*
|
|
||||||
* Creates a set of controls to input, sanitize and increase/decrease throughput
|
|
||||||
*
|
|
||||||
* How to use in your markup:
|
|
||||||
* <throughput-input params="{ value: anObservableToHoldTheValue, minimum: anObservableWithMinimum, maximum: anObservableWithMaximum }">
|
|
||||||
* </throughput-input>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parameters for this component
|
|
||||||
*/
|
|
||||||
export interface ThroughputInputParams {
|
|
||||||
/**
|
|
||||||
* Callback triggered when the template is bound to the component (for testing purposes)
|
|
||||||
*/
|
|
||||||
onTemplateReady?: () => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Observable to bind the Throughput value to
|
|
||||||
*/
|
|
||||||
value: ViewModels.Editable<number>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Text to use as id for testing
|
|
||||||
*/
|
|
||||||
testId: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Text to use as aria-label
|
|
||||||
*/
|
|
||||||
ariaLabel?: ko.Observable<string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum value in the range
|
|
||||||
*/
|
|
||||||
minimum: ko.Observable<number>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum value in the range
|
|
||||||
*/
|
|
||||||
maximum: ko.Observable<number>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Step value for increase/decrease
|
|
||||||
*/
|
|
||||||
step?: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Observable to bind the Throughput enabled status
|
|
||||||
*/
|
|
||||||
isEnabled?: ko.Observable<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should show pricing controls
|
|
||||||
*/
|
|
||||||
costsVisible: ko.Observable<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RU price
|
|
||||||
*/
|
|
||||||
requestUnitsUsageCost: ko.Computed<string>; // Our code assigns to ko.Computed, but unit test assigns to ko.Observable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* State of the spending acknowledge checkbox
|
|
||||||
*/
|
|
||||||
spendAckChecked?: ko.Observable<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* id of the spending acknowledge checkbox
|
|
||||||
*/
|
|
||||||
spendAckId?: ko.Observable<string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* spending acknowledge text
|
|
||||||
*/
|
|
||||||
spendAckText?: ko.Observable<string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show spending acknowledge controls
|
|
||||||
*/
|
|
||||||
spendAckVisible?: ko.Observable<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display * to the left of the label
|
|
||||||
*/
|
|
||||||
showAsMandatory: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If true, it will display a text to prompt users to use unlimited collections to go beyond max for fixed
|
|
||||||
*/
|
|
||||||
isFixed: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Label of the provisioned throughut control
|
|
||||||
*/
|
|
||||||
label: ko.Observable<string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Text of the info bubble for provisioned throughut control
|
|
||||||
*/
|
|
||||||
infoBubbleText?: ko.Observable<string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computed value that decides if value can exceed maximum allowable value
|
|
||||||
*/
|
|
||||||
canExceedMaximumValue?: ko.Computed<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CSS classes to apply on input element
|
|
||||||
*/
|
|
||||||
cssClass?: string;
|
|
||||||
|
|
||||||
isAutoPilotSelected: ko.Observable<boolean>;
|
|
||||||
throughputAutoPilotRadioId: string;
|
|
||||||
throughputProvisionedRadioId: string;
|
|
||||||
throughputModeRadioName: string;
|
|
||||||
maxAutoPilotThroughputSet: ViewModels.Editable<number>;
|
|
||||||
autoPilotUsageCost: ko.Computed<string>;
|
|
||||||
overrideWithAutoPilotSettings: ko.Observable<boolean>;
|
|
||||||
overrideWithProvisionedThroughputSettings: ko.Observable<boolean>;
|
|
||||||
freeTierExceedThroughputTooltip?: ko.Observable<string>;
|
|
||||||
freeTierExceedThroughputWarning?: ko.Observable<string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ThroughputInputViewModel extends WaitsForTemplateViewModel {
|
|
||||||
public ariaLabel: ko.Observable<string>;
|
|
||||||
public canExceedMaximumValue: ko.Computed<boolean>;
|
|
||||||
public step: ko.Computed<number>;
|
|
||||||
public testId: string;
|
|
||||||
public value: ViewModels.Editable<number>;
|
|
||||||
public minimum: ko.Observable<number>;
|
|
||||||
public maximum: ko.Observable<number>;
|
|
||||||
public isEnabled: ko.Observable<boolean>;
|
|
||||||
public cssClass: string;
|
|
||||||
public decreaseButtonAriaLabel: string;
|
|
||||||
public increaseButtonAriaLabel: string;
|
|
||||||
public costsVisible: ko.Observable<boolean>;
|
|
||||||
public requestUnitsUsageCost: ko.Computed<string>;
|
|
||||||
public spendAckChecked: ko.Observable<boolean>;
|
|
||||||
public spendAckId: ko.Observable<string>;
|
|
||||||
public spendAckText: ko.Observable<string>;
|
|
||||||
public spendAckVisible: ko.Observable<boolean>;
|
|
||||||
public showAsMandatory: boolean;
|
|
||||||
public infoBubbleText: string | ko.Observable<string>;
|
|
||||||
public label: ko.Observable<string>;
|
|
||||||
public isFixed: boolean;
|
|
||||||
public isAutoPilotSelected: ko.Observable<boolean>;
|
|
||||||
public throughputAutoPilotRadioId: string;
|
|
||||||
public throughputProvisionedRadioId: string;
|
|
||||||
public throughputModeRadioName: string;
|
|
||||||
public maxAutoPilotThroughputSet: ko.Observable<number>;
|
|
||||||
public autoPilotUsageCost: ko.Computed<string>;
|
|
||||||
public minAutoPilotThroughput: ko.Observable<number>;
|
|
||||||
public overrideWithAutoPilotSettings: ko.Observable<boolean>;
|
|
||||||
public overrideWithProvisionedThroughputSettings: ko.Observable<boolean>;
|
|
||||||
public isManualThroughputInputFieldRequired: ko.Computed<boolean>;
|
|
||||||
public isAutoscaleThroughputInputFieldRequired: ko.Computed<boolean>;
|
|
||||||
public freeTierExceedThroughputTooltip: ko.Observable<string>;
|
|
||||||
public freeTierExceedThroughputWarning: ko.Observable<string>;
|
|
||||||
public showFreeTierExceedThroughputTooltip: ko.Computed<boolean>;
|
|
||||||
public showFreeTierExceedThroughputWarning: ko.Computed<boolean>;
|
|
||||||
|
|
||||||
public constructor(options: ThroughputInputParams) {
|
|
||||||
super();
|
|
||||||
super.onTemplateReady((isTemplateReady: boolean) => {
|
|
||||||
if (isTemplateReady && options.onTemplateReady) {
|
|
||||||
options.onTemplateReady();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const params: ThroughputInputParams = options;
|
|
||||||
this.testId = params.testId || "ThroughputValue";
|
|
||||||
this.ariaLabel = ko.observable((params.ariaLabel && params.ariaLabel()) || "");
|
|
||||||
this.canExceedMaximumValue = params.canExceedMaximumValue || ko.computed(() => false);
|
|
||||||
this.isEnabled = params.isEnabled || ko.observable(true);
|
|
||||||
this.cssClass = params.cssClass || "textfontclr collid migration";
|
|
||||||
this.minimum = params.minimum;
|
|
||||||
this.maximum = params.maximum;
|
|
||||||
this.value = params.value;
|
|
||||||
this.costsVisible = options.costsVisible;
|
|
||||||
this.requestUnitsUsageCost = options.requestUnitsUsageCost;
|
|
||||||
this.spendAckChecked = options.spendAckChecked || ko.observable<boolean>(false);
|
|
||||||
this.spendAckId = options.spendAckId || ko.observable<string>();
|
|
||||||
this.spendAckText = options.spendAckText || ko.observable<string>();
|
|
||||||
this.spendAckVisible = options.spendAckVisible || ko.observable<boolean>(false);
|
|
||||||
this.showAsMandatory = !!options.showAsMandatory;
|
|
||||||
this.isFixed = !!options.isFixed;
|
|
||||||
this.infoBubbleText = options.infoBubbleText || ko.observable<string>();
|
|
||||||
this.label = options.label || ko.observable<string>();
|
|
||||||
this.isAutoPilotSelected = options.isAutoPilotSelected || ko.observable<boolean>(false);
|
|
||||||
this.isAutoPilotSelected.subscribe((value) => {
|
|
||||||
TelemetryProcessor.trace(Action.ToggleAutoscaleSetting, ActionModifiers.Mark, {
|
|
||||||
changedSelectedValueTo: value ? ActionModifiers.ToggleAutoscaleOn : ActionModifiers.ToggleAutoscaleOff,
|
|
||||||
dataExplorerArea: "Scale Tab V1",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.throughputAutoPilotRadioId = options.throughputAutoPilotRadioId;
|
|
||||||
this.throughputProvisionedRadioId = options.throughputProvisionedRadioId;
|
|
||||||
this.throughputModeRadioName = options.throughputModeRadioName;
|
|
||||||
this.overrideWithAutoPilotSettings = options.overrideWithAutoPilotSettings || ko.observable<boolean>(false);
|
|
||||||
this.overrideWithProvisionedThroughputSettings =
|
|
||||||
options.overrideWithProvisionedThroughputSettings || ko.observable<boolean>(false);
|
|
||||||
|
|
||||||
this.maxAutoPilotThroughputSet =
|
|
||||||
options.maxAutoPilotThroughputSet || ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput);
|
|
||||||
this.autoPilotUsageCost = options.autoPilotUsageCost;
|
|
||||||
this.minAutoPilotThroughput = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput);
|
|
||||||
|
|
||||||
this.step = ko.pureComputed(() => {
|
|
||||||
if (this.isAutoPilotSelected()) {
|
|
||||||
return AutoPilotUtils.autoPilotIncrementStep;
|
|
||||||
}
|
|
||||||
return params.step || ThroughputInputViewModel._defaultStep;
|
|
||||||
});
|
|
||||||
this.decreaseButtonAriaLabel = "Decrease throughput by " + this.step().toString();
|
|
||||||
this.increaseButtonAriaLabel = "Increase throughput by " + this.step().toString();
|
|
||||||
this.isManualThroughputInputFieldRequired = ko.pureComputed(() => this.isEnabled() && !this.isAutoPilotSelected());
|
|
||||||
this.isAutoscaleThroughputInputFieldRequired = ko.pureComputed(
|
|
||||||
() => this.isEnabled() && this.isAutoPilotSelected()
|
|
||||||
);
|
|
||||||
|
|
||||||
this.freeTierExceedThroughputTooltip = options.freeTierExceedThroughputTooltip || ko.observable<string>();
|
|
||||||
this.freeTierExceedThroughputWarning = options.freeTierExceedThroughputWarning || ko.observable<string>();
|
|
||||||
this.showFreeTierExceedThroughputTooltip = ko.pureComputed<boolean>(
|
|
||||||
() => !!this.freeTierExceedThroughputTooltip() && this.value() > 400
|
|
||||||
);
|
|
||||||
|
|
||||||
this.showFreeTierExceedThroughputWarning = ko.pureComputed<boolean>(
|
|
||||||
() => !!this.freeTierExceedThroughputWarning() && this.value() > 400
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public decreaseThroughput() {
|
|
||||||
let offerThroughput: number = this._getSanitizedValue();
|
|
||||||
|
|
||||||
if (offerThroughput > this.minimum()) {
|
|
||||||
offerThroughput -= this.step();
|
|
||||||
if (offerThroughput < this.minimum()) {
|
|
||||||
offerThroughput = this.minimum();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.value(offerThroughput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public increaseThroughput() {
|
|
||||||
let offerThroughput: number = this._getSanitizedValue();
|
|
||||||
|
|
||||||
if (offerThroughput < this.maximum() || this.canExceedMaximumValue()) {
|
|
||||||
offerThroughput += this.step();
|
|
||||||
if (offerThroughput > this.maximum() && !this.canExceedMaximumValue()) {
|
|
||||||
offerThroughput = this.maximum();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.value(offerThroughput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public onIncreaseKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
|
||||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
|
||||||
this.increaseThroughput();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
public onDecreaseKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
|
||||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
|
||||||
this.decreaseThroughput();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
private _getSanitizedValue(): number {
|
|
||||||
let throughput = this.value();
|
|
||||||
|
|
||||||
if (this.isAutoPilotSelected()) {
|
|
||||||
throughput = this.maxAutoPilotThroughputSet();
|
|
||||||
}
|
|
||||||
return isNaN(throughput) ? 0 : Number(throughput);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static _defaultStep: number = 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ThroughputInputComponentAutoPilotV3 = {
|
|
||||||
viewModel: ThroughputInputViewModel,
|
|
||||||
template: ThroughputInputComponentAutoscaleV3,
|
|
||||||
};
|
|
||||||