Compare commits

...

95 Commits

Author SHA1 Message Date
sunilyadav840
bb134a2441 fixed ts strict issue of useSubscription useDatabaseAccount markdown-cells text-file 2021-08-30 18:28:33 +05:30
victor-meng
8eeda41021 Move synapse link out of advanced section in add collection panel (#989) 2021-08-19 12:18:21 -07:00
Hardikkumar Nai
960cd9fc55 Resolve ESlint Controls (#990) 2021-08-19 12:16:35 -05:00
Hardikkumar Nai
9ec0ac9f54 Resolve ESLint Contracts (#986) 2021-08-19 12:15:52 -05:00
Steve Faulkner
b66aeb814a Polyfill Buffer (#988) 2021-08-18 21:12:40 -05:00
Tanuj Mittal
410f582378 Fix notebooksTemporarilyDown feature flag (#983) 2021-08-17 07:27:41 +05:30
Hardikkumar Nai
678ca51c77 Update to Webpack 5 (#964)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-08-16 15:44:40 -05:00
Steve Faulkner
2dfd90ca0f Disable Notebooks Test (#980) 2021-08-16 11:35:21 -05:00
Srinath Narayanan
4110be10bd Removing author from publish notebook payload (#966)
* removing author from publish payload

* fixed failing tests
2021-08-15 13:15:30 +05:30
Srinath Narayanan
6e55e397b3 Changes for Disabling notebook related features (#979)
* notebook removal changes

* fixed failing tests
2021-08-14 12:09:22 +05:30
Tanuj Mittal
24d0a16123 Disable notebooks temporarily (#978)
* Disable notebooks temporarily

* Updates
2021-08-14 07:03:58 +05:30
Tanuj Mittal
f8ac36962b Add Security Warning Bar for untrusted notebooks (#970)
* Add Security Warning Bar for untrusted notebooks

* Update

* Another update

* Add a snapshot test

* UX updates

* More updates

* Add tests

* Update test snapshot

* Update string

* Update security message
2021-08-10 23:54:26 +05:30
Sunil Kumar Yadav
51f3f9a718 Move inputTypeahead to react (#946)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-08-04 09:46:18 -05:00
victor-meng
ee4404c439 Fix enable synapse link error (#918)
* Fix enable synapse error

* Default all ARM requests to JSON

Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-08-02 21:11:42 -05:00
Steve Faulkner
b65456f754 Fix bug in Dialog State store (#969) 2021-08-02 20:54:30 -05:00
victor-meng
56699ccb1b Fix new resource tree (#962) 2021-07-30 16:23:36 -07:00
victor-meng
042f980b89 Replace window.confirm and window.alert with modal dialog (#965) 2021-07-30 10:27:27 -07:00
t-tarabhatia
7e0c4b7290 Add changes to Partition Key A/B Test (#954) 2021-07-29 08:48:03 -05:00
victor-meng
a66fc06dad Turn off react resource tree (#963) 2021-07-28 21:29:45 -07:00
victor-meng
a8bc821dec Add initializeGitHubRepos function in useNotebooks store (#957) 2021-07-23 18:44:24 -07:00
victor-meng
1394aae944 Fix validateCollectionId for new tables account (#958) 2021-07-23 18:44:16 -07:00
victor-meng
fecac5625a Migrate resource tree for resource token to react (#956) 2021-07-22 17:11:19 -07:00
victor-meng
dc21032d69 Clean up EditTableEntityPanel (#955) 2021-07-22 16:18:48 -07:00
siddjoshi-ms
39a67dbc98 Sqlx estimated cost calculation (#925)
Adds cost estimate for the SqlX services in the Dedicated Gateway blade (queries the new FetchPrices API to retrieve price data)
2021-07-22 10:48:19 -07:00
Sunil Kumar Yadav
ed9cf01b50 Fixed resourse tree collapse issue (#953) 2021-07-22 11:12:35 -05:00
vaidankarswapnil
e443d17b2e Migrate Index page to React (#952) 2021-07-22 10:19:17 -05:00
victor-meng
401660ae15 Fix connect to github (#951) 2021-07-21 17:47:55 -07:00
Steve Faulkner
c5e4ee9c2b Upgrade Playwright (#939) 2021-07-21 19:32:53 -05:00
victor-meng
913fec4e69 Improve e2e stability (#949) 2021-07-21 16:22:31 -07:00
victor-meng
6d46e48490 Migrate resource tree to react (#941) 2021-07-20 11:40:04 -07:00
victor-meng
afacde4041 Fix AddTableEntityPanel (#945)
* Fix AddTableEntityPanel

* Add CSS

* Fix snapshot
2021-07-19 22:00:33 -07:00
vaidankarswapnil
8a3929775b Strict mode SwitchAccount and SwitchSubscription files (#940) 2021-07-15 10:54:07 -05:00
vaidankarswapnil
b115bb34ca Fix GitHub repos panel issues (#875)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
Co-authored-by: Tanuj Mittal <tamitta@microsoft.com>
2021-07-15 09:44:25 -05:00
Sunil Kumar Yadav
397231dca2 Fixed typescript strict issue of Statusbar, hostedUtils, StringUtils etc (#785) 2021-07-15 09:35:51 -05:00
Hardikkumar Nai
0bbf9de963 Resolve ESLint Errors (#934) 2021-07-15 08:53:07 -05:00
Sunil Kumar Yadav
103b3bf6c9 fixed typescript strict of CqlUtilities.test.ts CapabilityUtils.ts file (#787)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-07-15 00:51:36 -05:00
Sunil Kumar Yadav
7dd8bd567f fixed typescript strict useSidePanel.ts PanelContainerComponent.tsx etc (#802)
* fixed typescript strict useSidePanel.ts PanelContainerComponent.tsx SchemaAnalyzerSplashScreen.tsx files

* update snapshot
2021-07-14 21:42:46 -05:00
Tanuj Mittal
416358f540 Cleanup Schema Analyzer feature flag (#938) 2021-07-15 06:42:14 +05:30
Tanuj Mittal
62b483d740 Fix connect to GitHub (#937) 2021-07-14 17:10:19 -05:00
Tanuj Mittal
c665c4bb7a Fix notebooks not rendering (#936) 2021-07-14 16:55:03 -05:00
t-tarabhatia
887618e77e UX Changes for Default Partition Key A/B Experiment (#928) 2021-07-14 14:10:45 -05:00
victor-meng
8d6ccf8356 import ComponentRegisterer in Explorer (#935) 2021-07-13 19:59:39 -05:00
Zachary Foster
4614ab3427 Adds disableLocalAuth check for listKeys call (#931) 2021-07-13 08:20:13 -04:00
Hardikkumar Nai
71113e403e Resolve ESlint Errors (#932) 2021-07-12 22:38:16 -05:00
Tanuj Mittal
854bd2c149 Use window messaging to pass sensitive data to terminal iframe (#929)
* Use window messaging to pass sensitive data to terminal iframe

* Address feedback

* Format

* Update

* Add tests
2021-07-13 03:18:13 +05:30
Hardikkumar Nai
cfce78242c Remove Explorer.openBrowseQueriesPane (#889) 2021-07-12 09:40:43 -05:00
Hardikkumar Nai
ee3488d3a9 Resolve more ESLint (#920)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-07-12 08:55:19 -05:00
Hardikkumar Nai
e8d320e505 Resolve more ESLint (#922)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-07-12 00:12:38 -05:00
victor-meng
f8ab0a82e0 Move tabs manager to zustand (#915) 2021-07-08 21:32:22 -07:00
Hardikkumar Nai
f4eef1b61b Resolve Eslint for Tables.Constants (#914)
* resolve_eslint_Tables.Constants

* correct spelling mistake
2021-07-08 21:10:29 -05:00
victor-meng
c486c1193e Fix scale component not showing in settings tab (#926) 2021-07-07 11:22:54 -07:00
victor-meng
db34024259 Move notebook flags to zustand (#912) 2021-07-06 15:21:23 -05:00
victor-meng
98d7bb37d5 Move resource token collection to useDatabases zustand store (#916) 2021-07-06 14:05:38 -05:00
Hardikkumar Nai
45d0b3f706 Resolve ESLint QueriesGridComponent (#923) 2021-07-06 09:12:41 -05:00
Hardikkumar Nai
a1d5648bbc Remove Explorer.openAddCollectionPane (#905)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-06-27 23:39:28 -05:00
Steve Faulkner
447db01647 TypeScript 4.3 (#910) 2021-06-24 19:14:26 -05:00
victor-meng
4d2a6999d4 Move selectedNode to zustand (#903) 2021-06-24 13:56:33 -05:00
Hardikkumar Nai
a7239c7579 Remove Explorer.openDeleteDatabaseConfirmationPane (#908)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-06-24 00:21:27 -05:00
Jordi Bunster
c1d4008895 Bypass ko<->React adapter in SettingsTab (#732)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-06-24 00:07:10 -05:00
victor-meng
59655eed5f Remove route handlers (#909)
* Remove tab handlers

* Fix tests
2021-06-23 23:54:37 -05:00
Laurent Nguyen
6b35ab03f2 Switch notebook editor from Monaco back to Code Mirror (#901) 2021-06-23 14:05:52 -05:00
victor-meng
738a02a0f3 Replace subscriptions in resource tree with useDatabases hook (#904) 2021-06-23 13:45:29 -05:00
vaidankarswapnil
b392bed1b0 Migrate Stored Procedure tab to react (#894)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-06-23 11:49:57 -05:00
vaidankarswapnil
f255387ccd Migrate Mongo Shell tab to React (#900) 2021-06-22 23:39:58 -05:00
Steve Faulkner
f9bd12eaa6 Fix for QueryTab Load More (#907) 2021-06-22 15:21:58 -05:00
Steve Faulkner
39215dc4de Remove unused GitHubReposComponentAdapter (#902) 2021-06-18 13:55:54 -05:00
victor-meng
96e6bba38b Move databases to zustand (#898) 2021-06-18 13:25:08 -05:00
Sunil Kumar Yadav
c9fa44f6f4 Fix UDF placeholder text (#899) 2021-06-17 10:54:40 -05:00
Sunil Kumar Yadav
05f59307c2 Migrate userDefinedFunctionTab to React (#860) 2021-06-16 19:09:22 -05:00
vaidankarswapnil
1d449e5b52 Implemented spinner in EditorReact component (#897) 2021-06-16 19:02:33 -05:00
Steve Faulkner
6f68c75257 Allow dynamic MSAL Authority (#896) 2021-06-16 09:13:11 -05:00
Sunil Kumar Yadav
914c372f5b Migrate Trigger tab to React (#855)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-06-15 21:53:50 -05:00
Steve Faulkner
af71a96d54 Add tree and treeitem roles to Resource Tree (#895)
* Add tree and treeitem roles to Resource Tree

* Updates
2021-06-15 14:52:21 -05:00
Steve Faulkner
239c7edf7b Fix Incorrect Image Links (#892) 2021-06-14 20:52:02 -05:00
Hardikkumar Nai
0c6324a4c1 Remove Explorer.openTableSelectQueryPanel (#881)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-06-14 16:57:47 -05:00
Hardikkumar Nai
615bfeaf48 Remove Explorer openEditTableEntityPanel and openAddTableEntityPanel (#887)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-06-14 16:34:37 -05:00
Steve Faulkner
3bc58a80e4 Remove Explorer.isHostedDataExplorerEnabled (#890) 2021-06-14 14:46:14 -05:00
vaidankarswapnil
5da9724deb Migrate Mongo Query Tab to React(#854)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-06-14 14:23:19 -05:00
vaidankarswapnil
999fad3bad Migrate Query Tab to React (#852)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-06-14 14:15:13 -05:00
victor-meng
baa3252ba8 Properly style CassandraAddCollectionPane (#862) 2021-06-11 14:25:05 -07:00
Hardikkumar Nai
959d34d88d Remove Explorer.openCassandraAddCollectionPane (#888) 2021-06-10 23:41:24 -05:00
Steve Faulkner
ce3c2fcfb6 Remove settings component container dependency (#886) 2021-06-10 19:29:41 -05:00
Steve Faulkner
0a1a2bf421 Remove QueryUtils.queryAll and fix test (#885) 2021-06-10 19:29:19 -05:00
victor-meng
b0bbeb188a Fix query tab/pane related issues (#879) 2021-06-10 17:17:11 -07:00
Steve Faulkner
fc9f287d0a Remove Explorer.configure (#884) 2021-06-10 19:02:07 -05:00
Steve Faulkner
006230262c Remvoe Explorer.isServerlessEnabled (#883) 2021-06-10 18:16:43 -05:00
Steve Faulkner
6de77a4fba Update strict mode files (#882) 2021-06-10 12:44:57 -05:00
Hardikkumar Nai
c980af9a5c Remove method Explorer.openSettingPane (#880) 2021-06-10 08:09:38 -05:00
Steve Faulkner
c632342a43 Remove window.jQuery (#878) 2021-06-09 16:03:42 -05:00
Steve Faulkner
bcc9f8dd32 Migrate remaining notification console methods to zustand (#873) 2021-06-09 15:11:12 -05:00
Steve Faulkner
fc9f4c5583 Migrate notebooks workspaces to generated clients (#876) 2021-06-09 15:08:10 -05:00
Steve Faulkner
8f6cac3d35 Remove Unused Splitter (#874) 2021-06-09 08:35:25 -05:00
Steve Faulkner
2c296ede35 Remove unused KO->React Adapters (#863) 2021-06-07 23:14:05 -05:00
victor-meng
16b09df5fa Hide provision throughput checkbox for serverless account (#861) 2021-06-07 23:05:41 -05:00
Srinath Narayanan
ee60f61cfe Mongo tabs UX fixes (#851)
* Fixed mongo tabs UX

* changed logic for new tab index

* moved index to tabs base

* removed getIndex method
2021-06-08 00:17:55 +05:30
304 changed files with 14967 additions and 18752 deletions

View File

@@ -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

View File

@@ -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,20 +71,11 @@ 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/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
@@ -135,7 +92,6 @@ src/Explorer/Tables/QueryBuilder/ClauseGroupViewModel.ts
src/Explorer/Tables/QueryBuilder/CustomTimestampHelper.ts src/Explorer/Tables/QueryBuilder/CustomTimestampHelper.ts
src/Explorer/Tables/QueryBuilder/QueryBuilderViewModel.ts src/Explorer/Tables/QueryBuilder/QueryBuilderViewModel.ts
src/Explorer/Tables/QueryBuilder/QueryClauseViewModel.ts src/Explorer/Tables/QueryBuilder/QueryClauseViewModel.ts
src/Explorer/Tables/QueryBuilder/QueryViewModel.ts
src/Explorer/Tables/TableDataClient.ts src/Explorer/Tables/TableDataClient.ts
src/Explorer/Tables/TableEntityProcessor.ts src/Explorer/Tables/TableEntityProcessor.ts
src/Explorer/Tables/Utilities.ts src/Explorer/Tables/Utilities.ts
@@ -145,170 +101,67 @@ 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/NotebookV2Tab.ts
src/Explorer/Tabs/QueryTab.test.ts
src/Explorer/Tabs/QueryTab.ts
src/Explorer/Tabs/QueryTablesTab.ts
src/Explorer/Tabs/ScriptTabBase.ts src/Explorer/Tabs/ScriptTabBase.ts
src/Explorer/Tabs/StoredProcedureTab.ts
src/Explorer/Tabs/TabComponents.ts src/Explorer/Tabs/TabComponents.ts
src/Explorer/Tabs/TabsBase.ts src/Explorer/Tabs/TabsBase.ts
src/Explorer/Tabs/TriggerTab.ts src/Explorer/Tabs/TriggerTab.ts
src/Explorer/Tabs/UserDefinedFunctionTab.ts src/Explorer/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/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/GitHubConnector.ts
src/GitHub/GitHubContentProvider.test.ts
src/GitHub/GitHubContentProvider.ts
src/GitHub/GitHubOAuthService.ts src/GitHub/GitHubOAuthService.ts
src/HostedExplorer.ts
src/Index.ts src/Index.ts
src/Juno/JunoClient.test.ts src/Juno/JunoClient.test.ts
src/Juno/JunoClient.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/GraphExplorer.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphExplorerAdapter.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/LeftPaneComponent.tsx src/Explorer/Graph/GraphExplorerComponent/LeftPaneComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.test.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/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/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

View File

@@ -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:

View File

@@ -1,4 +1,3 @@
{ {
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com", "JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com"
"enableSchemaAnalyzer": true
} }

View File

Before

Width:  |  Height:  |  Size: 842 B

After

Width:  |  Height:  |  Size: 842 B

View File

Before

Width:  |  Height:  |  Size: 371 B

After

Width:  |  Height:  |  Size: 371 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -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;

View File

@@ -724,45 +724,24 @@ execute-sproc-params-pane {
.results-container, .results-container,
.errors-container { .errors-container {
padding: @MediumSpace 0px 0px @MediumSpace;
height: 100%; height: 100%;
.flex-display(); .flex-display();
.flex-direction(); .flex-direction();
overflow: hidden; overflow: hidden;
.toggles {
height: @ToggleHeight;
width: @ToggleWidth;
margin-left: @MediumSpace;
&:focus {
.focus();
}
.tab {
margin-right: @MediumSpace;
}
.toggleSwitch {
.toggleSwitch();
}
.selectedToggle {
.selectedToggle();
}
.unselectedToggle {
.unselectedToggle();
}
}
.enterInputParameters { .enterInputParameters {
padding: @LargeSpace @MediumSpace; padding: @LargeSpace @MediumSpace;
} }
div[role="tabpanel"] {
height: 100%;
padding-bottom: 50px;
}
} }
.errors-container { .errors-container {
padding-left: (2 * @MediumSpace); padding-left: (2 * @MediumSpace);
padding: @MediumSpace 0px 0px @MediumSpace;
.errors-header { .errors-header {
font-weight: 700; font-weight: 700;
font-size: @DefaultFontSize; font-size: @DefaultFontSize;
@@ -3089,3 +3068,10 @@ settings-pane {
display: none; display: none;
height: 0px; height: 0px;
} }
.spinner {
width: 100%;
position: absolute;
z-index: 1;
background: white;
height: 100%;
}

View File

@@ -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;
}

View File

@@ -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;
}
} }

View File

@@ -1,9 +1,7 @@
@import "./Common/Constants"; @import "./Common/Constants";
.resourceTree { .resourceTree {
height: 100%; height: 100%;
width: 20%;
flex: 0 0 auto; flex: 0 0 auto;
.main { .main {
height: 100%; height: 100%;
@@ -46,14 +44,13 @@
} }
.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;
@@ -192,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;
} }
@@ -211,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 {

4774
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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",

View File

@@ -94,7 +94,8 @@ 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";
} }
export class AfecFeatures { export class AfecFeatures {
@@ -158,16 +159,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";
@@ -359,6 +350,11 @@ export class Notebook {
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 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.";
} }
export class SparkLibrary { export class SparkLibrary {

View File

@@ -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()", () => {

View File

@@ -5,7 +5,6 @@ import { Collection } from "../Contracts/ViewModels";
import DocumentId from "../Explorer/Tree/DocumentId"; import DocumentId from "../Explorer/Tree/DocumentId";
import { updateUserContext } from "../UserContext"; import { updateUserContext } from "../UserContext";
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient"; import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
const databaseId = "testDB"; const databaseId = "testDB";

View File

@@ -111,7 +111,7 @@ export function queryDocuments(
headers: response.headers, headers: response.headers,
}; };
} }
errorHandling(response, "querying documents", params); await errorHandling(response, "querying documents", params);
return undefined; return undefined;
}); });
} }
@@ -153,11 +153,11 @@ export function readDocument(
), ),
}, },
}) })
.then((response) => { .then(async (response) => {
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
return errorHandling(response, "reading document", params); return await errorHandling(response, "reading document", params);
}); });
} }
@@ -192,11 +192,11 @@ export function createDocument(
...authHeaders(), ...authHeaders(),
}, },
}) })
.then((response) => { .then(async (response) => {
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
return errorHandling(response, "creating document", params); return await errorHandling(response, "creating document", params);
}); });
} }
@@ -238,11 +238,11 @@ export function updateDocument(
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()), [CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
}, },
}) })
.then((response) => { .then(async (response) => {
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
return errorHandling(response, "updating document", params); return await errorHandling(response, "updating document", params);
}); });
} }
@@ -278,11 +278,11 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()), [CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
}, },
}) })
.then((response) => { .then(async (response) => {
if (response.ok) { if (response.ok) {
return undefined; return undefined;
} }
return errorHandling(response, "deleting document", params); return await errorHandling(response, "deleting document", params);
}); });
} }
@@ -325,11 +325,11 @@ export function createMongoCollectionWithProxy(
}, },
} }
) )
.then((response) => { .then(async (response) => {
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
return errorHandling(response, "creating collection", mongoParams); return await errorHandling(response, "creating collection", mongoParams);
}); });
} }

View File

@@ -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) {

View File

@@ -2,17 +2,22 @@ import React, { FunctionComponent } from "react";
import arrowLeftImg from "../../images/imgarrowlefticon.svg"; import arrowLeftImg from "../../images/imgarrowlefticon.svg";
import refreshImg from "../../images/refresh-cosmos.svg"; import refreshImg from "../../images/refresh-cosmos.svg";
import { AuthType } from "../AuthType"; 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 { userContext } from "../UserContext";
export interface ResourceTreeProps { export interface ResourceTreeContainerProps {
toggleLeftPaneExpanded: () => void; toggleLeftPaneExpanded: () => void;
isLeftPaneExpanded: boolean; isLeftPaneExpanded: boolean;
container: Explorer;
} }
export const ResourceTree: FunctionComponent<ResourceTreeProps> = ({ export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps> = ({
toggleLeftPaneExpanded, toggleLeftPaneExpanded,
isLeftPaneExpanded, isLeftPaneExpanded,
}: ResourceTreeProps): JSX.Element => { container,
}: ResourceTreeContainerProps): JSX.Element => {
return ( return (
<div id="main" className={isLeftPaneExpanded ? "main" : "hiddenMain"}> <div id="main" className={isLeftPaneExpanded ? "main" : "hiddenMain"}>
{/* Collections Window - - Start */} {/* Collections Window - - Start */}
@@ -48,9 +53,11 @@ export const ResourceTree: FunctionComponent<ResourceTreeProps> = ({
</div> </div>
</div> </div>
{userContext.authType === AuthType.ResourceToken ? ( {userContext.authType === AuthType.ResourceToken ? (
<div style={{ overflowY: "auto" }} data-bind="react:resourceTreeForResourceToken" /> <ResourceTokenTree />
) : ( ) : userContext.features.enableKoResourceTree ? (
<div style={{ overflowY: "auto" }} data-bind="react:resourceTree" /> <div style={{ overflowY: "auto" }} data-bind="react:resourceTree" />
) : (
<ResourceTree container={container} />
)} )}
</div> </div>
{/* Collections Window - End */} {/* Collections Window - End */}

View File

@@ -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);
}
} }

View File

@@ -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 () => {

View File

@@ -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,

View File

@@ -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: {

View File

@@ -27,7 +27,6 @@ export interface ConfigContext {
hostedExplorerURL: string; hostedExplorerURL: string;
armAPIVersion?: string; armAPIVersion?: string;
allowedJunoOrigins: string[]; allowedJunoOrigins: string[];
enableSchemaAnalyzer: boolean;
msalRedirectURI?: string; msalRedirectURI?: string;
} }
@@ -63,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 {
@@ -120,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) {

View File

@@ -9,6 +9,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;

View File

@@ -6,7 +6,7 @@ import {
UserDefinedFunctionDefinition, UserDefinedFunctionDefinition,
} from "@azure/cosmos"; } from "@azure/cosmos";
import Explorer from "../Explorer/Explorer"; import Explorer from "../Explorer/Explorer";
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleData } from "../Explorer/Menus/NotificationConsole/ConsoleData";
import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient"; import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient";
import ConflictId from "../Explorer/Tree/ConflictId"; import ConflictId from "../Explorer/Tree/ConflictId";
import DocumentId from "../Explorer/Tree/DocumentId"; import DocumentId from "../Explorer/Tree/DocumentId";
@@ -89,7 +89,6 @@ export interface Database extends TreeNode {
selectedSubnodeKind: ko.Observable<CollectionTabKind>; selectedSubnodeKind: ko.Observable<CollectionTabKind>;
selectDatabase(): void;
expandDatabase(): Promise<void>; expandDatabase(): Promise<void>;
collapseDatabase(): void; collapseDatabase(): void;
@@ -275,7 +274,6 @@ export interface TabOptions {
tabKind: CollectionTabKind; tabKind: CollectionTabKind;
title: string; title: string;
tabPath: string; tabPath: string;
hashLocation: string;
isTabsContentExpanded?: ko.Observable<boolean>; isTabsContentExpanded?: ko.Observable<boolean>;
onLoadStartKey?: number; onLoadStartKey?: number;
@@ -286,6 +284,7 @@ export interface TabOptions {
rid?: string; rid?: string;
node?: TreeNode; node?: TreeNode;
theme?: string; theme?: string;
index?: number;
} }
export interface DocumentsTabOptions extends TabOptions { export interface DocumentsTabOptions extends TabOptions {

View File

@@ -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);

View File

@@ -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]);
} }

View File

@@ -1,172 +0,0 @@
import AddCollectionIcon from "../../images/AddCollection.svg";
import AddSqlQueryIcon from "../../images/AddSqlQuery_16x16.svg";
import AddStoredProcedureIcon from "../../images/AddStoredProcedure.svg";
import AddTriggerIcon from "../../images/AddTrigger.svg";
import AddUdfIcon from "../../images/AddUdf.svg";
import DeleteCollectionIcon from "../../images/DeleteCollection.svg";
import DeleteDatabaseIcon from "../../images/DeleteDatabase.svg";
import DeleteSprocIcon from "../../images/DeleteSproc.svg";
import DeleteTriggerIcon from "../../images/DeleteTrigger.svg";
import DeleteUDFIcon from "../../images/DeleteUDF.svg";
import HostedTerminalIcon from "../../images/Hosted-Terminal.svg";
import * as ViewModels from "../Contracts/ViewModels";
import { userContext } from "../UserContext";
import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils";
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
import Explorer from "./Explorer";
import StoredProcedure from "./Tree/StoredProcedure";
import Trigger from "./Tree/Trigger";
import UserDefinedFunction from "./Tree/UserDefinedFunction";
export interface CollectionContextMenuButtonParams {
databaseId: string;
collectionId: string;
}
export interface DatabaseContextMenuButtonParams {
databaseId: string;
}
/**
* New resource tree (in ReactJS)
*/
export class ResourceTreeContextMenuButtonFactory {
public static createDatabaseContextMenu(container: Explorer, databaseId: string): TreeNodeMenuItem[] {
const items: TreeNodeMenuItem[] = [
{
iconSrc: AddCollectionIcon,
onClick: () => container.onNewCollectionClicked(databaseId),
label: `New ${getCollectionName()}`,
},
];
if (userContext.apiType !== "Tables") {
items.push({
iconSrc: DeleteDatabaseIcon,
onClick: () => container.openDeleteDatabaseConfirmationPane(),
label: `Delete ${getDatabaseName()}`,
styleClass: "deleteDatabaseMenuItem",
});
}
return items;
}
public static createCollectionContextMenuButton(
container: Explorer,
selectedCollection: ViewModels.Collection
): TreeNodeMenuItem[] {
const items: TreeNodeMenuItem[] = [];
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
items.push({
iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null),
label: "New SQL Query",
});
}
if (userContext.apiType === "Mongo") {
items.push({
iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null),
label: "New Query",
});
items.push({
iconSrc: HostedTerminalIcon,
onClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
if (container.isShellEnabled()) {
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
} else {
selectedCollection && selectedCollection.onNewMongoShellClick();
}
},
label: container.isShellEnabled() ? "Open Mongo Shell" : "New Shell",
});
}
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
items.push({
iconSrc: AddStoredProcedureIcon,
onClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null);
},
label: "New Stored Procedure",
});
items.push({
iconSrc: AddUdfIcon,
onClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, null);
},
label: "New UDF",
});
items.push({
iconSrc: AddTriggerIcon,
onClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, null);
},
label: "New Trigger",
});
}
items.push({
iconSrc: DeleteCollectionIcon,
onClick: () => container.openDeleteCollectionConfirmationPane(),
label: `Delete ${getCollectionName()}`,
styleClass: "deleteCollectionMenuItem",
});
return items;
}
public static createStoreProcedureContextMenuItems(
container: Explorer,
storedProcedure: StoredProcedure
): TreeNodeMenuItem[] {
if (userContext.apiType === "Cassandra") {
return [];
}
return [
{
iconSrc: DeleteSprocIcon,
onClick: () => storedProcedure.delete(),
label: "Delete Store Procedure",
},
];
}
public static createTriggerContextMenuItems(container: Explorer, trigger: Trigger): TreeNodeMenuItem[] {
if (userContext.apiType === "Cassandra") {
return [];
}
return [
{
iconSrc: DeleteTriggerIcon,
onClick: () => trigger.delete(),
label: "Delete Trigger",
},
];
}
public static createUserDefinedFunctionContextMenuItems(
container: Explorer,
userDefinedFunction: UserDefinedFunction
): TreeNodeMenuItem[] {
if (userContext.apiType === "Cassandra") {
return [];
}
return [
{
iconSrc: DeleteUDFIcon,
onClick: () => userDefinedFunction.delete(),
label: "Delete User Defined Function",
},
];
}
}

View 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",
},
];
};

View File

@@ -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 });
}; };

View File

@@ -121,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 {

View File

@@ -23,13 +23,75 @@ 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,
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,
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();
},
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 {

View File

@@ -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 {

View File

@@ -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}>

View File

@@ -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()));
}
}

View File

@@ -5,6 +5,9 @@
display: inline-block; display: inline-block;
width: 100%; width: 100%;
.input-type-head-text-field {
width: 100%;
}
textarea { textarea {
width: 100%; width: 100%;
line-height: 1; line-height: 1;
@@ -21,4 +24,11 @@
} }
} }
} }
.input-typeahead-chocies-container {
border: 1px solid lightgrey;
padding: 5px 10px 5px 10px;
cursor: pointer;
.choice-caption{
font-size: 14px;
}
}

View File

@@ -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,125 @@ 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>
<TextField
multiline={useTextarea}
rows={1}
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)}
/>
{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>
);
} }
} }

View File

@@ -1,61 +1,43 @@
// 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}
>
<StyledTextFieldBase
ariaLabel="Input query"
className="input-type-head-text-field"
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> </Stack>
</div> </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}
>
<StyledTextFieldBase
ariaLabel="Input query"
className="input-type-head-text-field"
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> </Stack>
</div> </div>
</div>
</div>
</span>
`; `;

View File

@@ -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],
])
);
}); });
}); });

View File

@@ -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;
} }
} }

View File

@@ -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>
`;

View File

@@ -55,7 +55,7 @@ export class NotebookViewerComponent
databaseAccountName: undefined, databaseAccountName: undefined,
defaultExperience: "NotebookViewer", defaultExperience: "NotebookViewer",
isReadOnly: true, isReadOnly: true,
cellEditorType: "monaco", cellEditorType: "codemirror",
autoSaveInterval: 365 * 24 * 3600 * 1000, // There is no way to turn off auto-save, set to 1 year autoSaveInterval: 365 * 24 * 3600 * 1000, // There is no way to turn off auto-save, set to 1 year
contentProvider: contents.JupyterContentProvider, // NotebookViewer only knows how to talk to Jupyter contents API contentProvider: contents.JupyterContentProvider, // NotebookViewer only knows how to talk to Jupyter contents API
}); });

View File

@@ -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
);
}, },
}, },
], ],

View File

@@ -38,7 +38,6 @@ describe("SettingsComponent", () => {
title: "Scale & Settings", title: "Scale & Settings",
tabPath: "", tabPath: "",
node: undefined, node: undefined,
hashLocation: "settings",
}), }),
}; };
@@ -127,7 +126,6 @@ describe("SettingsComponent", () => {
isDatabaseExpanded: undefined, isDatabaseExpanded: undefined,
isDatabaseShared: ko.computed(() => true), isDatabaseShared: ko.computed(() => true),
selectedSubnodeKind: undefined, selectedSubnodeKind: undefined,
selectDatabase: undefined,
expandDatabase: undefined, expandDatabase: undefined,
collapseDatabase: undefined, collapseDatabase: undefined,
loadCollections: undefined, loadCollections: undefined,

View File

@@ -17,7 +17,6 @@ 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 { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2"; import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
import "./SettingsComponent.less"; import "./SettingsComponent.less";
@@ -122,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;
@@ -134,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";
@@ -146,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();
} }
@@ -294,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 &&
@@ -884,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,
@@ -912,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,
@@ -965,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,

View File

@@ -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} /> : <></>;
}
}

View File

@@ -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: () => {

View File

@@ -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;

View File

@@ -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);
}); });

View File

@@ -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 => {

View File

@@ -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: () => {

View File

@@ -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;

View File

@@ -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,

View File

@@ -30,23 +30,8 @@ exports[`SettingsComponent renders 1`] = `
"container": Explorer { "container": Explorer {
"_isInitializingNotebooks": false, "_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function], "_resetNotebookWorkspace": [Function],
"canSaveQueries": [Function],
"collapsedResourceTreeWidth": 36,
"databases": [Function],
"isAccountReady": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
"isHostedDataExplorerEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isSchemaEnabled": [Function],
"isServerlessEnabled": [Function],
"isShellEnabled": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function], "isTabsContentExpanded": [Function],
"memoryUsageInfo": [Function],
"notebookBasePath": [Function],
"notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"provideFeedbackEmail": [Function], "provideFeedbackEmail": [Function],
@@ -54,43 +39,11 @@ exports[`SettingsComponent renders 1`] = `
"container": [Circular], "container": [Circular],
}, },
"refreshNotebookList": [Function], "refreshNotebookList": [Function],
"resourceTokenCollection": [Function],
"resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function],
"resourceTokenPartitionKey": [Function],
"resourceTree": ResourceTreeAdapter { "resourceTree": ResourceTreeAdapter {
"container": [Circular], "container": [Circular],
"copyNotebook": [Function], "copyNotebook": [Function],
"databaseCollectionIdMap": Map {},
"koSubsCollectionIdMap": Map {},
"koSubsDatabaseIdMap": Map {},
"parameters": [Function], "parameters": [Function],
}, },
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
"container": [Circular],
"parameters": [Function],
},
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setNotificationConsoleData": undefined,
"sparkClusterConnectionInfo": [Function],
"splitter": Splitter {
"bounds": Object {
"max": 400,
"min": 240,
},
"direction": "vertical",
"isCollapsed": [Function],
"leftSideId": "resourcetree",
"onResizeStart": [Function],
"onResizeStop": [Function],
"splitterId": "h_splitter1",
},
"tabsManager": TabsManager {
"activeTab": [Function],
"openedTabs": [Function],
},
}, },
"databaseId": "test", "databaseId": "test",
"defaultTtl": [Function], "defaultTtl": [Function],
@@ -110,73 +63,6 @@ exports[`SettingsComponent renders 1`] = `
"usageSizeInKB": [Function], "usageSizeInKB": [Function],
} }
} }
container={
Explorer {
"_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function],
"canSaveQueries": [Function],
"collapsedResourceTreeWidth": 36,
"databases": [Function],
"isAccountReady": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isHostedDataExplorerEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isSchemaEnabled": [Function],
"isServerlessEnabled": [Function],
"isShellEnabled": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
"memoryUsageInfo": [Function],
"notebookBasePath": [Function],
"notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
"container": [Circular],
},
"refreshNotebookList": [Function],
"resourceTokenCollection": [Function],
"resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function],
"resourceTokenPartitionKey": [Function],
"resourceTree": ResourceTreeAdapter {
"container": [Circular],
"copyNotebook": [Function],
"databaseCollectionIdMap": Map {},
"koSubsCollectionIdMap": Map {},
"koSubsDatabaseIdMap": Map {},
"parameters": [Function],
},
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
"container": [Circular],
"parameters": [Function],
},
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setNotificationConsoleData": undefined,
"sparkClusterConnectionInfo": [Function],
"splitter": Splitter {
"bounds": Object {
"max": 400,
"min": 240,
},
"direction": "vertical",
"isCollapsed": [Function],
"leftSideId": "resourcetree",
"onResizeStart": [Function],
"onResizeStop": [Function],
"splitterId": "h_splitter1",
},
"tabsManager": TabsManager {
"activeTab": [Function],
"openedTabs": [Function],
},
}
}
isAutoPilotSelected={false} isAutoPilotSelected={false}
isFixedContainer={false} isFixedContainer={false}
onAutoPilotSelected={[Function]} onAutoPilotSelected={[Function]}
@@ -211,23 +97,8 @@ exports[`SettingsComponent renders 1`] = `
"container": Explorer { "container": Explorer {
"_isInitializingNotebooks": false, "_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function], "_resetNotebookWorkspace": [Function],
"canSaveQueries": [Function],
"collapsedResourceTreeWidth": 36,
"databases": [Function],
"isAccountReady": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
"isHostedDataExplorerEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isSchemaEnabled": [Function],
"isServerlessEnabled": [Function],
"isShellEnabled": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function], "isTabsContentExpanded": [Function],
"memoryUsageInfo": [Function],
"notebookBasePath": [Function],
"notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"provideFeedbackEmail": [Function], "provideFeedbackEmail": [Function],
@@ -235,43 +106,11 @@ exports[`SettingsComponent renders 1`] = `
"container": [Circular], "container": [Circular],
}, },
"refreshNotebookList": [Function], "refreshNotebookList": [Function],
"resourceTokenCollection": [Function],
"resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function],
"resourceTokenPartitionKey": [Function],
"resourceTree": ResourceTreeAdapter { "resourceTree": ResourceTreeAdapter {
"container": [Circular], "container": [Circular],
"copyNotebook": [Function], "copyNotebook": [Function],
"databaseCollectionIdMap": Map {},
"koSubsCollectionIdMap": Map {},
"koSubsDatabaseIdMap": Map {},
"parameters": [Function], "parameters": [Function],
}, },
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
"container": [Circular],
"parameters": [Function],
},
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setNotificationConsoleData": undefined,
"sparkClusterConnectionInfo": [Function],
"splitter": Splitter {
"bounds": Object {
"max": 400,
"min": 240,
},
"direction": "vertical",
"isCollapsed": [Function],
"leftSideId": "resourcetree",
"onResizeStart": [Function],
"onResizeStop": [Function],
"splitterId": "h_splitter1",
},
"tabsManager": TabsManager {
"activeTab": [Function],
"openedTabs": [Function],
},
}, },
"databaseId": "test", "databaseId": "test",
"defaultTtl": [Function], "defaultTtl": [Function],
@@ -291,73 +130,6 @@ exports[`SettingsComponent renders 1`] = `
"usageSizeInKB": [Function], "usageSizeInKB": [Function],
} }
} }
container={
Explorer {
"_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function],
"canSaveQueries": [Function],
"collapsedResourceTreeWidth": 36,
"databases": [Function],
"isAccountReady": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isHostedDataExplorerEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isSchemaEnabled": [Function],
"isServerlessEnabled": [Function],
"isShellEnabled": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
"memoryUsageInfo": [Function],
"notebookBasePath": [Function],
"notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
"container": [Circular],
},
"refreshNotebookList": [Function],
"resourceTokenCollection": [Function],
"resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function],
"resourceTokenPartitionKey": [Function],
"resourceTree": ResourceTreeAdapter {
"container": [Circular],
"copyNotebook": [Function],
"databaseCollectionIdMap": Map {},
"koSubsCollectionIdMap": Map {},
"koSubsDatabaseIdMap": Map {},
"parameters": [Function],
},
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
"container": [Circular],
"parameters": [Function],
},
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setNotificationConsoleData": undefined,
"sparkClusterConnectionInfo": [Function],
"splitter": Splitter {
"bounds": Object {
"max": 400,
"min": 240,
},
"direction": "vertical",
"isCollapsed": [Function],
"leftSideId": "resourcetree",
"onResizeStart": [Function],
"onResizeStop": [Function],
"splitterId": "h_splitter1",
},
"tabsManager": TabsManager {
"activeTab": [Function],
"openedTabs": [Function],
},
}
}
geospatialConfigType="Geometry" geospatialConfigType="Geometry"
geospatialConfigTypeBaseline="Geometry" geospatialConfigTypeBaseline="Geometry"
isAnalyticalStorageEnabled={false} isAnalyticalStorageEnabled={false}

View File

@@ -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}

View File

@@ -58,7 +58,7 @@ export interface TreeComponentProps {
export class TreeComponent extends React.Component<TreeComponentProps> { export class TreeComponent extends React.Component<TreeComponentProps> {
public render(): JSX.Element { public render(): JSX.Element {
return ( return (
<div style={this.props.style} className={`treeComponent ${this.props.className}`}> <div style={this.props.style} className={`treeComponent ${this.props.className}`} role="tree">
<TreeNodeComponent paddingLeft={0} node={this.props.rootNode} generation={0} /> <TreeNodeComponent paddingLeft={0} node={this.props.rootNode} generation={0} />
</div> </div>
); );
@@ -172,6 +172,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
className={`${this.props.node.className || ""} main${generation} nodeItem ${showSelected ? "selected" : ""}`} className={`${this.props.node.className || ""} main${generation} nodeItem ${showSelected ? "selected" : ""}`}
onClick={(event: React.MouseEvent<HTMLDivElement>) => this.onNodeClick(event, node)} onClick={(event: React.MouseEvent<HTMLDivElement>) => this.onNodeClick(event, node)}
onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) => this.onNodeKeyPress(event, node)} onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) => this.onNodeKeyPress(event, node)}
role="treeitem"
> >
<div <div
className={`treeNodeHeader ${this.state.isMenuShowing ? "showingMenu" : ""}`} className={`treeNodeHeader ${this.state.isMenuShowing ? "showingMenu" : ""}`}

View File

@@ -3,6 +3,7 @@
exports[`TreeComponent renders a simple tree 1`] = ` exports[`TreeComponent renders a simple tree 1`] = `
<div <div
className="treeComponent tree" className="treeComponent tree"
role="tree"
> >
<TreeNodeComponent <TreeNodeComponent
generation={0} generation={0}
@@ -37,6 +38,7 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
className=" main2 nodeItem " className=" main2 nodeItem "
onClick={[Function]} onClick={[Function]}
onKeyPress={[Function]} onKeyPress={[Function]}
role="treeitem"
> >
<div <div
className="treeNodeHeader " className="treeNodeHeader "
@@ -137,6 +139,7 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
className="nodeClassname main12 nodeItem " className="nodeClassname main12 nodeItem "
onClick={[Function]} onClick={[Function]}
onKeyPress={[Function]} onKeyPress={[Function]}
role="treeitem"
> >
<div <div
className="treeNodeHeader " className="treeNodeHeader "
@@ -285,6 +288,7 @@ exports[`TreeNodeComponent renders loading icon 1`] = `
className=" main2 nodeItem " className=" main2 nodeItem "
onClick={[Function]} onClick={[Function]}
onKeyPress={[Function]} onKeyPress={[Function]}
role="treeitem"
> >
<div <div
className="treeNodeHeader " className="treeNodeHeader "
@@ -356,6 +360,7 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
className="nodeClassname main12 nodeItem " className="nodeClassname main12 nodeItem "
onClick={[Function]} onClick={[Function]}
onKeyPress={[Function]} onKeyPress={[Function]}
role="treeitem"
> >
<div <div
className="treeNodeHeader " className="treeNodeHeader "
@@ -523,6 +528,7 @@ exports[`TreeNodeComponent renders unsorted children by default 1`] = `
className=" main2 nodeItem " className=" main2 nodeItem "
onClick={[Function]} onClick={[Function]}
onKeyPress={[Function]} onKeyPress={[Function]}
role="treeitem"
> >
<div <div
className="treeNodeHeader " className="treeNodeHeader "

View File

@@ -2,22 +2,22 @@ jest.mock("../Graph/GraphExplorerComponent/GremlinClient");
jest.mock("../../Common/dataAccess/createCollection"); jest.mock("../../Common/dataAccess/createCollection");
jest.mock("../../Common/dataAccess/createDocument"); jest.mock("../../Common/dataAccess/createDocument");
import * as ko from "knockout"; import * as ko from "knockout";
import Q from "q";
import { createDocument } from "../../Common/dataAccess/createDocument"; import { createDocument } from "../../Common/dataAccess/createDocument";
import { DatabaseAccount } from "../../Contracts/DataModels"; import { DatabaseAccount } from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { updateUserContext } from "../../UserContext"; import { updateUserContext } from "../../UserContext";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { useDatabases } from "../useDatabases";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator"; import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
describe("ContainerSampleGenerator", () => { describe("ContainerSampleGenerator", () => {
const createExplorerStub = (database: ViewModels.Database): Explorer => { let explorerStub: Explorer;
const explorerStub = {} as Explorer;
explorerStub.databases = ko.observableArray<ViewModels.Database>([database]); beforeAll(() => {
explorerStub.findDatabaseWithId = () => database; explorerStub = {
explorerStub.refreshAllDatabases = () => Q.resolve(); refreshAllDatabases: () => {},
return explorerStub; } as Explorer;
}; });
beforeEach(() => { beforeEach(() => {
(createDocument as jest.Mock).mockResolvedValue(undefined); (createDocument as jest.Mock).mockResolvedValue(undefined);
@@ -59,8 +59,7 @@ describe("ContainerSampleGenerator", () => {
loadCollections: () => {}, loadCollections: () => {},
} as ViewModels.Database; } as ViewModels.Database;
database.findCollectionWithId = () => collection; database.findCollectionWithId = () => collection;
useDatabases.getState().addDatabases([database]);
const explorerStub = createExplorerStub(database);
const generator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub); const generator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub);
generator.setData(sampleData); generator.setData(sampleData);
@@ -108,8 +107,8 @@ describe("ContainerSampleGenerator", () => {
} as ViewModels.Database; } as ViewModels.Database;
database.findCollectionWithId = () => collection; database.findCollectionWithId = () => collection;
collection.databaseId = database.id(); collection.databaseId = database.id();
useDatabases.getState().addDatabases([database]);
const explorerStub = createExplorerStub(database);
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
properties: { properties: {
@@ -126,7 +125,6 @@ describe("ContainerSampleGenerator", () => {
it("should not create any sample for Mongo API account", async () => { it("should not create any sample for Mongo API account", async () => {
const experience = "Sample generation not supported for this API Mongo"; const experience = "Sample generation not supported for this API Mongo";
const explorerStub = createExplorerStub(undefined);
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
properties: { properties: {
@@ -141,7 +139,6 @@ describe("ContainerSampleGenerator", () => {
it("should not create any sample for Table API account", async () => { it("should not create any sample for Table API account", async () => {
const experience = "Sample generation not supported for this API Tables"; const experience = "Sample generation not supported for this API Tables";
const explorerStub = createExplorerStub(undefined);
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
properties: { properties: {
@@ -163,7 +160,6 @@ describe("ContainerSampleGenerator", () => {
}, },
} as DatabaseAccount, } as DatabaseAccount,
}); });
const explorerStub = createExplorerStub(undefined);
// Rejects with error that contains experience // Rejects with error that contains experience
await expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience); await expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
}); });

View File

@@ -7,6 +7,7 @@ import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"
import GraphTab from ".././Tabs/GraphTab"; import GraphTab from ".././Tabs/GraphTab";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient"; import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
import { useDatabases } from "../useDatabases";
interface SampleDataFile extends DataModels.CreateCollectionParams { interface SampleDataFile extends DataModels.CreateCollectionParams {
data: any[]; data: any[];
@@ -59,7 +60,7 @@ export class ContainerSampleGenerator {
await createCollection(createRequest); await createCollection(createRequest);
await this.container.refreshAllDatabases(); await this.container.refreshAllDatabases();
const database = this.container.findDatabaseWithId(this.sampleDataFile.databaseId); const database = useDatabases.getState().findDatabaseWithId(this.sampleDataFile.databaseId);
if (!database) { if (!database) {
return undefined; return undefined;
} }

View File

@@ -2,6 +2,7 @@ import * as ko from "knockout";
import * as sinon from "sinon"; import * as sinon from "sinon";
import { Collection, Database } from "../../Contracts/ViewModels"; import { Collection, Database } from "../../Contracts/ViewModels";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { useDatabases } from "../useDatabases";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator"; import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
import { DataSamplesUtil } from "./DataSamplesUtil"; import { DataSamplesUtil } from "./DataSamplesUtil";
@@ -16,8 +17,7 @@ describe("DataSampleUtils", () => {
collections: ko.observableArray<Collection>([collection]), collections: ko.observableArray<Collection>([collection]),
} as Database; } as Database;
const explorer = {} as Explorer; const explorer = {} as Explorer;
explorer.databases = ko.observableArray<Database>([database]); useDatabases.getState().addDatabases([database]);
explorer.showOkModalDialog = () => {};
const dataSamplesUtil = new DataSamplesUtil(explorer); const dataSamplesUtil = new DataSamplesUtil(explorer);
const fakeGenerator = sinon.createStubInstance<ContainerSampleGenerator>(ContainerSampleGenerator as any); const fakeGenerator = sinon.createStubInstance<ContainerSampleGenerator>(ContainerSampleGenerator as any);

View File

@@ -1,7 +1,9 @@
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils"; import { logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { useDialog } from "../Controls/Dialog";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { useDatabases } from "../useDatabases";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator"; import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
export class DataSamplesUtil { export class DataSamplesUtil {
@@ -17,9 +19,9 @@ export class DataSamplesUtil {
const databaseName = generator.getDatabaseId(); const databaseName = generator.getDatabaseId();
const containerName = generator.getCollectionId(); const containerName = generator.getCollectionId();
if (this.hasContainer(databaseName, containerName, this.container.databases())) { if (this.hasContainer(databaseName, containerName, useDatabases.getState().databases)) {
const msg = `The container ${containerName} in database ${databaseName} already exists. Please delete it and retry.`; const msg = `The container ${containerName} in database ${databaseName} already exists. Please delete it and retry.`;
this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg); useDialog.getState().showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
logConsoleError(msg); logConsoleError(msg);
return; return;
} }
@@ -28,7 +30,7 @@ export class DataSamplesUtil {
.createSampleContainerAsync() .createSampleContainerAsync()
.catch((error) => logConsoleError(`Error creating sample container: ${error}`)); .catch((error) => logConsoleError(`Error creating sample container: ${error}`));
const msg = `The sample ${containerName} in database ${databaseName} has been successfully created.`; const msg = `The sample ${containerName} in database ${databaseName} has been successfully created.`;
this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg); useDialog.getState().showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
logConsoleInfo(msg); logConsoleInfo(msg);
} }

View File

@@ -1,43 +0,0 @@
jest.mock("./../Common/dataAccess/deleteDatabase");
jest.mock("./../Shared/Telemetry/TelemetryProcessor");
import * as ko from "knockout";
import { deleteDatabase } from "./../Common/dataAccess/deleteDatabase";
import * as ViewModels from "./../Contracts/ViewModels";
import Explorer from "./Explorer";
describe("Explorer.isLastDatabase() and Explorer.isLastNonEmptyDatabase()", () => {
let explorer: Explorer;
beforeAll(() => {
(deleteDatabase as jest.Mock).mockResolvedValue(undefined);
});
beforeEach(() => {
explorer = new Explorer();
});
it("should be true if only 1 database", () => {
const database = {} as ViewModels.Database;
explorer.databases = ko.observableArray<ViewModels.Database>([database]);
expect(explorer.isLastDatabase()).toBe(true);
});
it("should be false if only 2 databases", () => {
const database = {} as ViewModels.Database;
const database2 = {} as ViewModels.Database;
explorer.databases = ko.observableArray<ViewModels.Database>([database, database2]);
expect(explorer.isLastDatabase()).toBe(false);
});
it("should be false if not last empty database", () => {
const database = {} as ViewModels.Database;
explorer.databases = ko.observableArray<ViewModels.Database>([database]);
expect(explorer.isLastNonEmptyDatabase()).toBe(false);
});
it("should be true if last non empty database", () => {
const database = {} as ViewModels.Database;
database.collections = ko.observableArray<ViewModels.Collection>([{} as ViewModels.Collection]);
explorer.databases = ko.observableArray<ViewModels.Database>([database]);
expect(explorer.isLastNonEmptyDatabase()).toBe(true);
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -4,12 +4,12 @@
*/ */
import * as React from "react"; import * as React from "react";
import { NeighborVertexBasicInfo, EditedEdges, GraphNewEdgeData, PossibleVertex } from "./GraphExplorer";
import * as GraphUtil from "./GraphUtil";
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
import DeleteIcon from "../../../../images/delete.svg";
import AddPropertyIcon from "../../../../images/Add-property.svg"; import AddPropertyIcon from "../../../../images/Add-property.svg";
import DeleteIcon from "../../../../images/delete.svg";
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement"; import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
import { EditedEdges, GraphNewEdgeData, NeighborVertexBasicInfo, PossibleVertex } from "./GraphExplorer";
import * as GraphUtil from "./GraphUtil";
export interface EditorNeighborsComponentProps { export interface EditorNeighborsComponentProps {
isSource: boolean; isSource: boolean;
@@ -83,11 +83,11 @@ export class EditorNeighborsComponent extends React.Component<EditorNeighborsCom
} }
private removeCurrentNeighborEdge(index: number): void { private removeCurrentNeighborEdge(index: number): void {
let sources = this.props.editedNeighbors.currentNeighbors; const sources = this.props.editedNeighbors.currentNeighbors;
let id = sources[index].edgeId; const id = sources[index].edgeId;
sources.splice(index, 1); sources.splice(index, 1);
let droppedIds = this.props.editedNeighbors.droppedIds; const droppedIds = this.props.editedNeighbors.droppedIds;
droppedIds.push(id); droppedIds.push(id);
this.onUpdateEdges(); this.onUpdateEdges();
} }
@@ -215,7 +215,7 @@ export class EditorNeighborsComponent extends React.Component<EditorNeighborsCom
</td> </td>
<td className="actionCol"> <td className="actionCol">
<span className="rightPaneTrashIcon rightPaneBtns"> <span className="rightPaneTrashIcon rightPaneBtns">
<img src={DeleteIcon} alt="Delete" onClick={(e) => this.removeAddedEdgeToNeighbor(index)} /> <img src={DeleteIcon} alt="Delete" onClick={() => this.removeAddedEdgeToNeighbor(index)} />
</span> </span>
</td> </td>
</tr> </tr>

View File

@@ -1,12 +1,11 @@
import React from "react";
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import { GraphHighlightedNodeData, EditedProperties } from "./GraphExplorer"; import React from "react";
import { EditorNodePropertiesComponent, EditorNodePropertiesComponentProps } from "./EditorNodePropertiesComponent"; import { EditorNodePropertiesComponent, EditorNodePropertiesComponentProps } from "./EditorNodePropertiesComponent";
describe("<EditorNodePropertiesComponent />", () => { describe("<EditorNodePropertiesComponent />", () => {
// Tests that: single value prop is rendered with a textbox and a delete button // Tests that: single value prop is rendered with a textbox and a delete button
// multi-value prop only a delete button (cannot be edited) // multi-value prop only a delete button (cannot be edited)
const onUpdateProperties = jest.fn();
it("renders component", () => { it("renders component", () => {
const props: EditorNodePropertiesComponentProps = { const props: EditorNodePropertiesComponentProps = {
editedProperties: { editedProperties: {
@@ -24,7 +23,6 @@ describe("<EditorNodePropertiesComponent />", () => {
{ value: true, type: "boolean" }, { value: true, type: "boolean" },
{ value: false, type: "boolean" }, { value: false, type: "boolean" },
{ value: undefined, type: "null" }, { value: undefined, type: "null" },
{ value: null, type: "null" },
], ],
}, },
], ],
@@ -41,14 +39,13 @@ describe("<EditorNodePropertiesComponent />", () => {
{ value: true, type: "boolean" }, { value: true, type: "boolean" },
{ value: false, type: "boolean" }, { value: false, type: "boolean" },
{ value: undefined, type: "null" }, { value: undefined, type: "null" },
{ value: null, type: "null" },
], ],
}, },
], ],
addedProperties: [], addedProperties: [],
droppedKeys: [], droppedKeys: [],
}, },
onUpdateProperties: (editedProperties: EditedProperties): void => {}, onUpdateProperties,
}; };
const wrapper = shallow(<EditorNodePropertiesComponent {...props} />); const wrapper = shallow(<EditorNodePropertiesComponent {...props} />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
@@ -81,7 +78,7 @@ describe("<EditorNodePropertiesComponent />", () => {
addedProperties: [], addedProperties: [],
droppedKeys: [], droppedKeys: [],
}, },
onUpdateProperties: (editedProperties: EditedProperties): void => {}, onUpdateProperties,
}; };
const wrapper = shallow(<EditorNodePropertiesComponent {...props} />); const wrapper = shallow(<EditorNodePropertiesComponent {...props} />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();

View File

@@ -4,12 +4,12 @@
*/ */
import * as React from "react"; import * as React from "react";
import * as ViewModels from "../../../Contracts/ViewModels";
import { EditedProperties } from "./GraphExplorer";
import DeleteIcon from "../../../../images/delete.svg";
import AddIcon from "../../../../images/Add-property.svg"; import AddIcon from "../../../../images/Add-property.svg";
import { ReadOnlyNodePropertiesComponent } from "./ReadOnlyNodePropertiesComponent"; import DeleteIcon from "../../../../images/delete.svg";
import * as ViewModels from "../../../Contracts/ViewModels";
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement"; import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
import { EditedProperties } from "./GraphExplorer";
import { ReadOnlyNodePropertiesComponent } from "./ReadOnlyNodePropertiesComponent";
export interface EditorNodePropertiesComponentProps { export interface EditorNodePropertiesComponentProps {
editedProperties: EditedProperties; editedProperties: EditedProperties;
@@ -48,7 +48,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
const editedProperties = this.props.editedProperties; const editedProperties = this.props.editedProperties;
// search for it // search for it
for (let i = 0; i < editedProperties.existingProperties.length; i++) { for (let i = 0; i < editedProperties.existingProperties.length; i++) {
let ip = editedProperties.existingProperties[i]; const ip = editedProperties.existingProperties[i];
if (ip.key === key) { if (ip.key === key) {
editedProperties.existingProperties.splice(i, 1); editedProperties.existingProperties.splice(i, 1);
editedProperties.droppedKeys.push(key); editedProperties.droppedKeys.push(key);
@@ -60,7 +60,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
private removeAddedProperty(index: number): void { private removeAddedProperty(index: number): void {
const editedProperties = this.props.editedProperties; const editedProperties = this.props.editedProperties;
let ap = editedProperties.addedProperties; const ap = editedProperties.addedProperties;
ap.splice(index, 1); ap.splice(index, 1);
this.props.onUpdateProperties(editedProperties); this.props.onUpdateProperties(editedProperties);
@@ -68,7 +68,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
private addProperty(): void { private addProperty(): void {
const editedProperties = this.props.editedProperties; const editedProperties = this.props.editedProperties;
let ap = editedProperties.addedProperties; const ap = editedProperties.addedProperties;
ap.push({ key: "", values: [{ value: "", type: EditorNodePropertiesComponent.DEFAULT_PROPERTY_TYPE }] }); ap.push({ key: "", values: [{ value: "", type: EditorNodePropertiesComponent.DEFAULT_PROPERTY_TYPE }] });
this.props.onUpdateProperties(editedProperties); this.props.onUpdateProperties(editedProperties);
} }
@@ -126,7 +126,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
onChange={(e) => { onChange={(e) => {
singleValue.type = e.target.value as ViewModels.InputPropertyValueTypeString; singleValue.type = e.target.value as ViewModels.InputPropertyValueTypeString;
if (singleValue.type === "null") { if (singleValue.type === "null") {
singleValue.value = null; singleValue.value = undefined;
} }
this.props.onUpdateProperties(this.props.editedProperties); this.props.onUpdateProperties(this.props.editedProperties);
}} }}
@@ -144,7 +144,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
className="rightPaneTrashIcon rightPaneBtns" className="rightPaneTrashIcon rightPaneBtns"
as="span" as="span"
aria-label="Delete property" aria-label="Delete property"
onActivated={(e) => this.removeExistingProperty(key)} onActivated={() => this.removeExistingProperty(key)}
> >
<img src={DeleteIcon} alt="Delete" /> <img src={DeleteIcon} alt="Delete" />
</AccessibleElement> </AccessibleElement>
@@ -166,7 +166,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
className="rightPaneTrashIcon rightPaneBtns" className="rightPaneTrashIcon rightPaneBtns"
as="span" as="span"
aria-label="Remove existing property" aria-label="Remove existing property"
onActivated={(e) => this.removeExistingProperty(nodeProp.key)} onActivated={() => this.removeExistingProperty(nodeProp.key)}
> >
<img src={DeleteIcon} alt="Delete" /> <img src={DeleteIcon} alt="Delete" />
</AccessibleElement> </AccessibleElement>
@@ -206,7 +206,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
onChange={(e) => { onChange={(e) => {
firstValue.value = e.target.value; firstValue.value = e.target.value;
if (firstValue.type === "null") { if (firstValue.type === "null") {
firstValue.value = null; firstValue.value = undefined;
} }
this.props.onUpdateProperties(this.props.editedProperties); this.props.onUpdateProperties(this.props.editedProperties);
}} }}
@@ -235,7 +235,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
className="rightPaneTrashIcon rightPaneBtns" className="rightPaneTrashIcon rightPaneBtns"
as="span" as="span"
aria-label="Remove property" aria-label="Remove property"
onActivated={(e) => this.removeAddedProperty(index)} onActivated={() => this.removeAddedProperty(index)}
> >
<img src={DeleteIcon} alt="Delete" /> <img src={DeleteIcon} alt="Delete" />
</AccessibleElement> </AccessibleElement>

View File

@@ -10,7 +10,7 @@ import { queryDocumentsPage } from "../../../Common/dataAccess/queryDocumentsPag
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import * as StorageUtility from "../../../Shared/StorageUtility"; import * as StorageUtility from "../../../Shared/StorageUtility";
import { TabComponent } from "../../Controls/Tabs/TabComponent"; import { TabComponent } from "../../Controls/Tabs/TabComponent";
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../../Menus/NotificationConsole/ConsoleData";
import GraphTab from "../../Tabs/GraphTab"; import GraphTab from "../../Tabs/GraphTab";
import * as D3ForceGraph from "./D3ForceGraph"; import * as D3ForceGraph from "./D3ForceGraph";
import { GraphData } from "./GraphData"; import { GraphData } from "./GraphData";

View File

@@ -18,7 +18,7 @@ import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Ut
import { EditorReact } from "../../Controls/Editor/EditorReact"; import { EditorReact } from "../../Controls/Editor/EditorReact";
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent"; import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
import * as TabComponent from "../../Controls/Tabs/TabComponent"; import * as TabComponent from "../../Controls/Tabs/TabComponent";
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../../Menus/NotificationConsole/ConsoleData";
import { IGraphConfig } from "../../Tabs/GraphTab"; import { IGraphConfig } from "../../Tabs/GraphTab";
import { ArraysByKeyCache } from "./ArraysByKeyCache"; import { ArraysByKeyCache } from "./ArraysByKeyCache";
import * as D3ForceGraph from "./D3ForceGraph"; import * as D3ForceGraph from "./D3ForceGraph";

View File

@@ -1,74 +0,0 @@
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import * as ViewModels from "../../../Contracts/ViewModels";
import { IGraphConfig } from "../../Tabs/GraphTab";
import { GraphAccessor, GraphExplorer } from "./GraphExplorer";
interface Parameter {
onIsNewVertexDisabledChange: (isEnabled: boolean) => void;
onGraphAccessorCreated: (instance: GraphAccessor) => void;
onIsFilterQueryLoading: (isFilterQueryLoading: boolean) => void;
onIsValidQuery: (isValidQuery: boolean) => void;
onIsPropertyEditing: (isEditing: boolean) => void;
onIsGraphDisplayed: (isDisplayed: boolean) => void;
onResetDefaultGraphConfigValues: () => void;
collectionPartitionKeyProperty: string;
graphBackendEndpoint: string;
databaseId: string;
collectionId: string;
masterKey: string;
onLoadStartKey: number;
onLoadStartKeyChange: (newKey: number) => void;
resourceId: string;
igraphConfigUiData: ViewModels.IGraphConfigUiData;
igraphConfig: IGraphConfig;
setIConfigUiData?: (data: string[]) => void;
}
interface IGraphExplorerProps {
isChanged: boolean;
}
interface IGraphExplorerStates {
isChangedState: boolean;
}
export interface GraphExplorerAdapter
extends ReactAdapter,
React.Component<IGraphExplorerProps, IGraphExplorerStates> {}
export class GraphExplorerAdapter implements ReactAdapter {
public params: Parameter;
public parameters = {};
public isNewVertexDisabled: boolean;
public constructor(params: Parameter, props?: IGraphExplorerProps) {
this.params = params;
}
public renderComponent(): JSX.Element {
return (
<GraphExplorer
onIsNewVertexDisabledChange={this.params.onIsNewVertexDisabledChange}
onGraphAccessorCreated={this.params.onGraphAccessorCreated}
onIsFilterQueryLoadingChange={this.params.onIsFilterQueryLoading}
onIsValidQueryChange={this.params.onIsValidQuery}
onIsPropertyEditing={this.params.onIsPropertyEditing}
onIsGraphDisplayed={this.params.onIsGraphDisplayed}
onResetDefaultGraphConfigValues={this.params.onResetDefaultGraphConfigValues}
collectionPartitionKeyProperty={this.params.collectionPartitionKeyProperty}
graphBackendEndpoint={this.params.graphBackendEndpoint}
databaseId={this.params.databaseId}
collectionId={this.params.collectionId}
masterKey={this.params.masterKey}
onLoadStartKey={this.params.onLoadStartKey}
onLoadStartKeyChange={this.params.onLoadStartKeyChange}
resourceId={this.params.resourceId}
igraphConfigUiData={this.params.igraphConfigUiData}
igraphConfig={this.params.igraphConfig}
setIConfigUiData={this.params.setIConfigUiData}
/>
);
}
}

View File

@@ -1,9 +1,7 @@
import * as GraphUtil from "./GraphUtil";
import { GraphData, GremlinVertex, GremlinEdge } from "./GraphData";
import * as sinon from "sinon"; import * as sinon from "sinon";
import { GraphData, GremlinEdge, GremlinVertex } from "./GraphData";
import { GraphExplorer } from "./GraphExplorer"; import { GraphExplorer } from "./GraphExplorer";
window.$ = window.jQuery = require("jquery"); import * as GraphUtil from "./GraphUtil";
const OUT_E_MATCHER = "g\\.V\\(.*\\).outE\\(\\).*\\.as\\('e'\\).inV\\(\\)\\.as\\('v'\\)\\.select\\('e', *'v'\\)"; const OUT_E_MATCHER = "g\\.V\\(.*\\).outE\\(\\).*\\.as\\('e'\\).inV\\(\\)\\.as\\('v'\\)\\.select\\('e', *'v'\\)";
const IN_E_MATCHER = "g\\.V\\(.*\\).inE\\(\\).*\\.as\\('e'\\).outV\\(\\)\\.as\\('v'\\)\\.select\\('e', *'v'\\)"; const IN_E_MATCHER = "g\\.V\\(.*\\).inE\\(\\).*\\.as\\('e'\\).outV\\(\\)\\.as\\('v'\\)\\.select\\('e', *'v'\\)";

View File

@@ -5,21 +5,26 @@
*/ */
import * as React from "react"; import * as React from "react";
import { GraphHighlightedNodeData, EditedProperties, EditedEdges, PossibleVertex } from "./GraphExplorer";
import { CollapsiblePanel } from "../../Controls/CollapsiblePanel/CollapsiblePanel";
import { ReadOnlyNodePropertiesComponent } from "./ReadOnlyNodePropertiesComponent";
import { EditorNodePropertiesComponent } from "./EditorNodePropertiesComponent";
import { ReadOnlyNeighborsComponent } from "./ReadOnlyNeighborsComponent";
import * as ViewModels from "../../../Contracts/ViewModels";
import { Item } from "../../Controls/InputTypeahead/InputTypeaheadComponent";
import * as EditorNeighbors from "./EditorNeighborsComponent";
import EditIcon from "../../../../images/edit.svg";
import DeleteIcon from "../../../../images/delete.svg";
import CheckIcon from "../../../../images/check.svg";
import CancelIcon from "../../../../images/cancel.svg"; import CancelIcon from "../../../../images/cancel.svg";
import { GraphExplorer } from "./GraphExplorer"; import CheckIcon from "../../../../images/check-1.svg";
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent"; import DeleteIcon from "../../../../images/delete.svg";
import EditIcon from "../../../../images/edit-1.svg";
import * as ViewModels from "../../../Contracts/ViewModels";
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement"; import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
import { CollapsiblePanel } from "../../Controls/CollapsiblePanel/CollapsiblePanel";
import { Item } from "../../Controls/InputTypeahead/InputTypeaheadComponent";
import { ConsoleDataType } from "../../Menus/NotificationConsole/ConsoleData";
import * as EditorNeighbors from "./EditorNeighborsComponent";
import { EditorNodePropertiesComponent } from "./EditorNodePropertiesComponent";
import {
EditedEdges,
EditedProperties,
GraphExplorer,
GraphHighlightedNodeData,
PossibleVertex,
} from "./GraphExplorer";
import { ReadOnlyNeighborsComponent } from "./ReadOnlyNeighborsComponent";
import { ReadOnlyNodePropertiesComponent } from "./ReadOnlyNodePropertiesComponent";
export enum Mode { export enum Mode {
READONLY_PROP, READONLY_PROP,

View File

@@ -1,6 +1,6 @@
import * as React from "react"; import * as React from "react";
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
import CloseIcon from "../../../../images/close-black.svg"; import CloseIcon from "../../../../images/close-black.svg";
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
export interface QueryContainerComponentProps { export interface QueryContainerComponentProps {
initialQuery: string; initialQuery: string;
@@ -82,7 +82,7 @@ export class QueryContainerComponent extends React.Component<
<button <button
type="button" type="button"
className="filterbtnstyle queryButton" className="filterbtnstyle queryButton"
onClick={(e) => this.props.onExecuteClick(this.state.query)} onClick={() => this.props.onExecuteClick(this.state.query)}
disabled={this.props.isLoading || !QueryContainerComponent.isQueryValid(this.state.query)} disabled={this.props.isLoading || !QueryContainerComponent.isQueryValid(this.state.query)}
> >
Execute Gremlin Query Execute Gremlin Query

View File

@@ -4,9 +4,9 @@
*/ */
import * as React from "react"; import * as React from "react";
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
import { GraphHighlightedNodeData, NeighborVertexBasicInfo } from "./GraphExplorer"; import { GraphHighlightedNodeData, NeighborVertexBasicInfo } from "./GraphExplorer";
import * as GraphUtil from "./GraphUtil"; import * as GraphUtil from "./GraphUtil";
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
export interface ReadOnlyNeighborsComponentProps { export interface ReadOnlyNeighborsComponentProps {
node: GraphHighlightedNodeData; node: GraphHighlightedNodeData;
@@ -48,7 +48,7 @@ export class ReadOnlyNeighborsComponent extends React.Component<ReadOnlyNeighbor
className="clickableLink" className="clickableLink"
as="a" as="a"
aria-label={_neighbor.name} aria-label={_neighbor.name}
onActivated={(e) => this.props.selectNode(_neighbor.id)} onActivated={() => this.props.selectNode(_neighbor.id)}
title={GraphUtil.getNeighborTitle(_neighbor)} title={GraphUtil.getNeighborTitle(_neighbor)}
> >
{_neighbor.name} {_neighbor.name}

View File

@@ -4,8 +4,8 @@
*/ */
import * as React from "react"; import * as React from "react";
import { GraphHighlightedNodeData } from "./GraphExplorer";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { GraphHighlightedNodeData } from "./GraphExplorer";
export interface ReadOnlyNodePropertiesComponentProps { export interface ReadOnlyNodePropertiesComponentProps {
node: GraphHighlightedNodeData; node: GraphHighlightedNodeData;

View File

@@ -37,7 +37,7 @@ exports[`<EditorNodePropertiesComponent /> renders component 1`] = `
</td> </td>
<td <td
className="valueCol" className="valueCol"
title="efgh, 1234, true, false, undefined, null" title="efgh, 1234, true, false, undefined"
> >
<div <div
className="propertyValue" className="propertyValue"
@@ -69,12 +69,6 @@ exports[`<EditorNodePropertiesComponent /> renders component 1`] = `
> >
undefined undefined
</div> </div>
<div
className="propertyValue isNull"
key="null"
>
null
</div>
</td> </td>
</tr> </tr>
<tr <tr
@@ -178,12 +172,6 @@ exports[`<EditorNodePropertiesComponent /> renders component 1`] = `
> >
undefined undefined
</div> </div>
<div
className="propertyValue isNull"
key="null"
>
null
</div>
</td> </td>
<td /> <td />
<td <td

View File

@@ -8,9 +8,10 @@ import * as React from "react";
import create, { UseStore } from "zustand"; import create, { UseStore } from "zustand";
import { StyleConstants } from "../../../Common/Constants"; import { StyleConstants } from "../../../Common/Constants";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { useObservable } from "../../../hooks/useObservable"; import { useTabs } from "../../../hooks/useTabs";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { useSelectedNode } from "../../useSelectedNode";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory"; import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
import * as CommandBarUtil from "./CommandBarUtil"; import * as CommandBarUtil from "./CommandBarUtil";
@@ -29,13 +30,13 @@ export const useCommandBar: UseStore<CommandBarStore> = create((set) => ({
})); }));
export const CommandBar: React.FC<Props> = ({ container }: Props) => { export const CommandBar: React.FC<Props> = ({ container }: Props) => {
useObservable(container.selectedNode); const selectedNodeState = useSelectedNode();
const buttons = useCommandBar((state) => state.contextButtons); const buttons = useCommandBar((state) => state.contextButtons);
const backgroundColor = StyleConstants.BaseLight; const backgroundColor = StyleConstants.BaseLight;
const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(container); const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(container, selectedNodeState);
const contextButtons = (buttons || []).concat( const contextButtons = (buttons || []).concat(
CommandBarComponentButtonFactory.createContextCommandBarButtons(container) CommandBarComponentButtonFactory.createContextCommandBarButtons(container, selectedNodeState)
); );
const controlButtons = CommandBarComponentButtonFactory.createControlCommandBarButtons(container); const controlButtons = CommandBarComponentButtonFactory.createControlCommandBarButtons(container);
@@ -53,8 +54,8 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor); const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true)); uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
if (container.tabsManager.activeTab()?.tabKind === ViewModels.CollectionTabKind.NotebookV2) { if (useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker", container.memoryUsageInfo)); uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
} }
return ( return (

View File

@@ -1,17 +1,24 @@
import * as ko from "knockout"; import * as ko from "knockout";
import { AuthType } from "../../../AuthType"; import { AuthType } from "../../../AuthType";
import { DatabaseAccount } from "../../../Contracts/DataModels"; import { DatabaseAccount } from "../../../Contracts/DataModels";
import { CollectionBase } from "../../../Contracts/ViewModels";
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService"; import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
import { updateUserContext } from "../../../UserContext"; import { updateUserContext } from "../../../UserContext";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import NotebookManager from "../../Notebook/NotebookManager"; import NotebookManager from "../../Notebook/NotebookManager";
import { useNotebook } from "../../Notebook/useNotebook";
import { useDatabases } from "../../useDatabases";
import { useSelectedNode } from "../../useSelectedNode";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory"; import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
describe("CommandBarComponentButtonFactory tests", () => { describe("CommandBarComponentButtonFactory tests", () => {
let mockExplorer: Explorer; let mockExplorer: Explorer;
afterEach(() => useSelectedNode.getState().setSelectedNode(undefined));
describe("Enable Azure Synapse Link Button", () => { describe("Enable Azure Synapse Link Button", () => {
const enableAzureSynapseLinkBtnLabel = "Enable Azure Synapse Link"; const enableAzureSynapseLinkBtnLabel = "Enable Azure Synapse Link";
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer; mockExplorer = {} as Explorer;
@@ -22,18 +29,10 @@ describe("CommandBarComponentButtonFactory tests", () => {
}, },
} as DatabaseAccount, } as DatabaseAccount,
}); });
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isNotebookEnabled = ko.observable(false);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
mockExplorer.isRunningOnNationalCloud = () => false;
}); });
it("Account is not serverless - button should be visible", () => { it("Account is not serverless - button should be visible", () => {
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const enableAzureSynapseLinkBtn = buttons.find( const enableAzureSynapseLinkBtn = buttons.find(
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel (button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
); );
@@ -41,9 +40,14 @@ describe("CommandBarComponentButtonFactory tests", () => {
}); });
it("Account is serverless - button should be hidden", () => { it("Account is serverless - button should be hidden", () => {
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => true); updateUserContext({
databaseAccount: {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); properties: {
capabilities: [{ name: "EnableServerless" }],
},
} as DatabaseAccount,
});
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableAzureSynapseLinkBtn = buttons.find( const enableAzureSynapseLinkBtn = buttons.find(
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel (button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
); );
@@ -53,72 +57,77 @@ describe("CommandBarComponentButtonFactory tests", () => {
describe("Enable notebook button", () => { describe("Enable notebook button", () => {
const enableNotebookBtnLabel = "Enable Notebooks (Preview)"; const enableNotebookBtnLabel = "Enable Notebooks (Preview)";
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer; mockExplorer = {} as Explorer;
updateUserContext({ updateUserContext({
portalEnv: "prod",
databaseAccount: { databaseAccount: {
properties: { properties: {
capabilities: [{ name: "EnableTable" }], capabilities: [{ name: "EnableTable" }],
}, },
} as DatabaseAccount, } as DatabaseAccount,
}); });
mockExplorer.isSynapseLinkUpdating = ko.observable(false); });
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; afterEach(() => {
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false); updateUserContext({
portalEnv: "prod",
});
useNotebook.getState().setIsNotebookEnabled(false);
useNotebook.getState().setIsNotebooksEnabledForAccount(false);
}); });
it("Notebooks is already enabled - button should be hidden", () => { it("Notebooks is already enabled - button should be hidden", () => {
mockExplorer.isNotebookEnabled = ko.observable(true); useNotebook.getState().setIsNotebookEnabled(true);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true); useNotebook.getState().setIsNotebooksEnabledForAccount(true);
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
expect(enableNotebookBtn).toBeUndefined(); expect(enableNotebookBtn).toBeUndefined();
}); });
it("Account is running on one of the national clouds - button should be hidden", () => { it("Account is running on one of the national clouds - button should be hidden", () => {
mockExplorer.isNotebookEnabled = ko.observable(false); updateUserContext({
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false); portalEnv: "mooncake",
mockExplorer.isRunningOnNationalCloud = ko.observable(true); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
expect(enableNotebookBtn).toBeUndefined(); expect(enableNotebookBtn).toBeUndefined();
}); });
it("Notebooks is not enabled but is available - button should be shown and enabled", () => { it("Notebooks is not enabled but is available - button should be shown and enabled", () => {
mockExplorer.isNotebookEnabled = ko.observable(false); useNotebook.getState().setIsNotebooksEnabledForAccount(true);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
expect(enableNotebookBtn).toBeDefined();
expect(enableNotebookBtn.disabled).toBe(false); //TODO: modify once notebooks are available
expect(enableNotebookBtn.tooltipText).toBe(""); expect(enableNotebookBtn).toBeUndefined();
//expect(enableNotebookBtn).toBeDefined();
//expect(enableNotebookBtn.disabled).toBe(false);
//expect(enableNotebookBtn.tooltipText).toBe("");
}); });
it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => { it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => {
mockExplorer.isNotebookEnabled = ko.observable(false); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
expect(enableNotebookBtn).toBeDefined();
expect(enableNotebookBtn.disabled).toBe(true); //TODO: modify once notebooks are available
expect(enableNotebookBtn.tooltipText).toBe( expect(enableNotebookBtn).toBeUndefined();
"Notebooks are not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks." //expect(enableNotebookBtn).toBeDefined();
); //expect(enableNotebookBtn.disabled).toBe(true);
//expect(enableNotebookBtn.tooltipText).toBe(
// "Notebooks are not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."
//);
}); });
}); });
describe("Open Mongo Shell button", () => { describe("Open Mongo Shell button", () => {
const openMongoShellBtnLabel = "Open Mongo Shell"; const openMongoShellBtnLabel = "Open Mongo Shell";
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer; mockExplorer = {} as Explorer;
@@ -129,87 +138,93 @@ describe("CommandBarComponentButtonFactory tests", () => {
}, },
} as DatabaseAccount, } as DatabaseAccount,
}); });
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
mockExplorer.isShellEnabled = ko.observable(true);
}); });
afterAll(() => { afterAll(() => {
updateUserContext({ updateUserContext({
apiType: "SQL", apiType: "SQL",
}); });
useNotebook.getState().setIsShellEnabled(false);
}); });
beforeEach(() => { beforeEach(() => {
updateUserContext({ updateUserContext({
apiType: "Mongo", apiType: "Mongo",
}); });
mockExplorer.isNotebookEnabled = ko.observable(false); useNotebook.getState().setIsShellEnabled(true);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false); });
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
mockExplorer.isShellEnabled = ko.observable(true); afterEach(() => {
useNotebook.getState().setIsNotebookEnabled(false);
useNotebook.getState().setIsNotebooksEnabledForAccount(false);
}); });
it("Mongo Api not available - button should be hidden", () => { it("Mongo Api not available - button should be hidden", () => {
updateUserContext({ updateUserContext({
apiType: "SQL", apiType: "SQL",
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined(); expect(openMongoShellBtn).toBeUndefined();
}); });
it("Running on a national cloud - button should be hidden", () => { it("Running on a national cloud - button should be hidden", () => {
mockExplorer.isRunningOnNationalCloud = ko.observable(true); updateUserContext({
portalEnv: "mooncake",
});
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined(); expect(openMongoShellBtn).toBeUndefined();
}); });
it("Notebooks is not enabled and is unavailable - button should be hidden", () => { it("Notebooks is not enabled and is unavailable - button should be hidden", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined(); expect(openMongoShellBtn).toBeUndefined();
}); });
it("Notebooks is not enabled and is available - button should be hidden", () => { it("Notebooks is not enabled and is available - button should be hidden", () => {
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true); useNotebook.getState().setIsNotebooksEnabledForAccount(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined(); expect(openMongoShellBtn).toBeUndefined();
}); });
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => { it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
mockExplorer.isNotebookEnabled = ko.observable(true); useNotebook.getState().setIsNotebookEnabled(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeDefined(); expect(openMongoShellBtn).toBeDefined();
expect(openMongoShellBtn.disabled).toBe(false);
expect(openMongoShellBtn.tooltipText).toBe(""); //TODO: modify once notebooks are available
expect(openMongoShellBtn.disabled).toBe(true);
//expect(openMongoShellBtn.disabled).toBe(false);
//expect(openMongoShellBtn.tooltipText).toBe("");
}); });
it("Notebooks is enabled and is available - button should be shown and enabled", () => { it("Notebooks is enabled and is available - button should be shown and enabled", () => {
mockExplorer.isNotebookEnabled = ko.observable(true); useNotebook.getState().setIsNotebookEnabled(true);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true); useNotebook.getState().setIsNotebooksEnabledForAccount(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeDefined(); expect(openMongoShellBtn).toBeDefined();
expect(openMongoShellBtn.disabled).toBe(false);
expect(openMongoShellBtn.tooltipText).toBe(""); //TODO: modify once notebooks are available
expect(openMongoShellBtn.disabled).toBe(true);
//expect(openMongoShellBtn.disabled).toBe(false);
//expect(openMongoShellBtn.tooltipText).toBe("");
}); });
it("Notebooks is enabled and is available, terminal is unavailable due to ipRules - button should be hidden", () => { it("Notebooks is enabled and is available, terminal is unavailable due to ipRules - button should be hidden", () => {
mockExplorer.isNotebookEnabled = ko.observable(true); useNotebook.getState().setIsNotebookEnabled(true);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true); useNotebook.getState().setIsNotebooksEnabledForAccount(true);
mockExplorer.isShellEnabled = ko.observable(false); useNotebook.getState().setIsShellEnabled(false);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined(); expect(openMongoShellBtn).toBeUndefined();
}); });
@@ -217,6 +232,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
describe("Open Cassandra Shell button", () => { describe("Open Cassandra Shell button", () => {
const openCassandraShellBtnLabel = "Open Cassandra Shell"; const openCassandraShellBtnLabel = "Open Cassandra Shell";
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer; mockExplorer = {} as Explorer;
@@ -227,10 +243,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
}, },
} as DatabaseAccount, } as DatabaseAccount,
}); });
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
}); });
beforeEach(() => { beforeEach(() => {
@@ -241,9 +253,11 @@ describe("CommandBarComponentButtonFactory tests", () => {
}, },
} as DatabaseAccount, } as DatabaseAccount,
}); });
mockExplorer.isNotebookEnabled = ko.observable(false); });
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
mockExplorer.isRunningOnNationalCloud = ko.observable(false); afterEach(() => {
useNotebook.getState().setIsNotebookEnabled(false);
useNotebook.getState().setIsNotebooksEnabledForAccount(false);
}); });
it("Cassandra Api not available - button should be hidden", () => { it("Cassandra Api not available - button should be hidden", () => {
@@ -254,59 +268,68 @@ describe("CommandBarComponentButtonFactory tests", () => {
}, },
} as DatabaseAccount, } as DatabaseAccount,
}); });
console.log(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined(); expect(openCassandraShellBtn).toBeUndefined();
}); });
it("Running on a national cloud - button should be hidden", () => { it("Running on a national cloud - button should be hidden", () => {
mockExplorer.isRunningOnNationalCloud = ko.observable(true); updateUserContext({
portalEnv: "mooncake",
});
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined(); expect(openCassandraShellBtn).toBeUndefined();
}); });
it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => { it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined(); expect(openCassandraShellBtn).toBeUndefined();
}); });
it("Notebooks is not enabled and is available - button should be shown and enabled", () => { it("Notebooks is not enabled and is available - button should be shown and enabled", () => {
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true); useNotebook.getState().setIsNotebooksEnabledForAccount(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined(); expect(openCassandraShellBtn).toBeUndefined();
}); });
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => { it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
mockExplorer.isNotebookEnabled = ko.observable(true); useNotebook.getState().setIsNotebookEnabled(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeDefined(); expect(openCassandraShellBtn).toBeDefined();
expect(openCassandraShellBtn.disabled).toBe(false);
expect(openCassandraShellBtn.tooltipText).toBe(""); //TODO: modify once notebooks are available
expect(openCassandraShellBtn.disabled).toBe(true);
//expect(openCassandraShellBtn.disabled).toBe(false);
//expect(openCassandraShellBtn.tooltipText).toBe("");
}); });
it("Notebooks is enabled and is available - button should be shown and enabled", () => { it("Notebooks is enabled and is available - button should be shown and enabled", () => {
mockExplorer.isNotebookEnabled = ko.observable(true); useNotebook.getState().setIsNotebookEnabled(true);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true); useNotebook.getState().setIsNotebooksEnabledForAccount(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeDefined(); expect(openCassandraShellBtn).toBeDefined();
expect(openCassandraShellBtn.disabled).toBe(false);
expect(openCassandraShellBtn.tooltipText).toBe(""); //TODO: modify once notebooks are available
expect(openCassandraShellBtn.disabled).toBe(true);
//expect(openCassandraShellBtn.disabled).toBe(false);
//expect(openCassandraShellBtn.tooltipText).toBe("");
}); });
}); });
describe("GitHub buttons", () => { describe("GitHub buttons", () => {
const connectToGitHubBtnLabel = "Connect to GitHub"; const connectToGitHubBtnLabel = "Connect to GitHub";
const manageGitHubSettingsBtnLabel = "Manage GitHub settings"; const manageGitHubSettingsBtnLabel = "Manage GitHub settings";
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer; mockExplorer = {} as Explorer;
@@ -318,36 +341,28 @@ describe("CommandBarComponentButtonFactory tests", () => {
} as DatabaseAccount, } as DatabaseAccount,
}); });
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
mockExplorer.notebookManager = new NotebookManager(); mockExplorer.notebookManager = new NotebookManager();
mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined); mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined);
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
});
beforeEach(() => {
mockExplorer.isNotebookEnabled = ko.observable(false);
}); });
afterEach(() => { afterEach(() => {
jest.resetAllMocks(); jest.resetAllMocks();
useNotebook.getState().setIsNotebookEnabled(false);
}); });
it("Notebooks is enabled and GitHubOAuthService is not logged in - connect to github button should be visible", () => { it("Notebooks is enabled and GitHubOAuthService is not logged in - connect to github button should be visible", () => {
mockExplorer.isNotebookEnabled = ko.observable(true); useNotebook.getState().setIsNotebookEnabled(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel); const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel);
expect(connectToGitHubBtn).toBeDefined(); expect(connectToGitHubBtn).toBeDefined();
}); });
it("Notebooks is enabled and GitHubOAuthService is logged in - manage github settings button should be visible", () => { it("Notebooks is enabled and GitHubOAuthService is logged in - manage github settings button should be visible", () => {
mockExplorer.isNotebookEnabled = ko.observable(true); useNotebook.getState().setIsNotebookEnabled(true);
mockExplorer.notebookManager.gitHubOAuthService.isLoggedIn = jest.fn().mockReturnValue(true); mockExplorer.notebookManager.gitHubOAuthService.isLoggedIn = jest.fn().mockReturnValue(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const manageGitHubSettingsBtn = buttons.find( const manageGitHubSettingsBtn = buttons.find(
(button) => button.commandButtonLabel === manageGitHubSettingsBtnLabel (button) => button.commandButtonLabel === manageGitHubSettingsBtnLabel
); );
@@ -355,7 +370,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
}); });
it("Notebooks is not enabled - connect to github and manage github settings buttons should be hidden", () => { it("Notebooks is not enabled - connect to github and manage github settings buttons should be hidden", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel); const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel);
expect(connectToGitHubBtn).toBeUndefined(); expect(connectToGitHubBtn).toBeUndefined();
@@ -368,11 +383,14 @@ describe("CommandBarComponentButtonFactory tests", () => {
}); });
describe("Resource token", () => { describe("Resource token", () => {
const mockCollection = { id: ko.observable("test") } as CollectionBase;
useSelectedNode.getState().setSelectedNode(mockCollection);
useDatabases.setState({ resourceTokenCollection: mockCollection });
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer; mockExplorer = {} as Explorer;
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isResourceTokenCollectionNodeSelected = ko.computed(() => true);
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
updateUserContext({ updateUserContext({
authType: AuthType.ResourceToken, authType: AuthType.ResourceToken,
}); });
@@ -384,7 +402,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
kind: "DocumentDB", kind: "DocumentDB",
} as DatabaseAccount, } as DatabaseAccount,
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
expect(buttons.length).toBe(2); expect(buttons.length).toBe(2);
expect(buttons[0].commandButtonLabel).toBe("New SQL Query"); expect(buttons[0].commandButtonLabel).toBe("New SQL Query");
expect(buttons[0].disabled).toBe(false); expect(buttons[0].disabled).toBe(false);

View File

@@ -22,18 +22,32 @@ import * as Constants from "../../../Common/Constants";
import { configContext, Platform } from "../../../ConfigContext"; import { configContext, Platform } from "../../../ConfigContext";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { useSidePanel } from "../../../hooks/useSidePanel"; import { useSidePanel } from "../../../hooks/useSidePanel";
import { JunoClient } from "../../../Juno/JunoClient";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils"; import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils";
import { isServerlessAccount } from "../../../Utils/CapabilityUtils";
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { useNotebook } from "../../Notebook/useNotebook";
import { OpenFullScreen } from "../../OpenFullScreen"; import { OpenFullScreen } from "../../OpenFullScreen";
import { AddDatabasePanel } from "../../Panes/AddDatabasePanel/AddDatabasePanel";
import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane";
import { GitHubReposPanel } from "../../Panes/GitHubReposPanel/GitHubReposPanel";
import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane"; import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane";
import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane";
import { SetupNoteBooksPanel } from "../../Panes/SetupNotebooksPanel/SetupNotebooksPanel";
import { useDatabases } from "../../useDatabases";
import { SelectedNodeState } from "../../useSelectedNode";
let counter = 0; let counter = 0;
export function createStaticCommandBarButtons(container: Explorer): CommandButtonComponentProps[] { export function createStaticCommandBarButtons(
container: Explorer,
selectedNodeState: SelectedNodeState
): CommandButtonComponentProps[] {
if (userContext.authType === AuthType.ResourceToken) { if (userContext.authType === AuthType.ResourceToken) {
return createStaticCommandBarButtonsForResourceToken(container); return createStaticCommandBarButtonsForResourceToken(container, selectedNodeState);
} }
const newCollectionBtn = createNewCollectionGroup(container); const newCollectionBtn = createNewCollectionGroup(container);
@@ -53,49 +67,66 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
newCollectionBtn.children.push(newDatabaseBtn); newCollectionBtn.children.push(newDatabaseBtn);
} }
if (useNotebook.getState().isNotebookEnabled) {
buttons.push(createDivider()); buttons.push(createDivider());
const notebookButtons: CommandButtonComponentProps[] = [];
if (container.isNotebookEnabled()) {
const newNotebookButton = createNewNotebookButton(container); const newNotebookButton = createNewNotebookButton(container);
newNotebookButton.children = [createNewNotebookButton(container), createuploadNotebookButton(container)]; newNotebookButton.children = [createNewNotebookButton(container), createuploadNotebookButton(container)];
buttons.push(newNotebookButton); notebookButtons.push(newNotebookButton);
if (container.notebookManager?.gitHubOAuthService) { if (container.notebookManager?.gitHubOAuthService) {
buttons.push(createManageGitHubAccountButton(container)); notebookButtons.push(createManageGitHubAccountButton(container));
} }
buttons.push(createOpenTerminalButton(container)); notebookButtons.push(createOpenTerminalButton(container));
buttons.push(createNotebookWorkspaceResetButton(container)); notebookButtons.push(createNotebookWorkspaceResetButton(container));
if ( if (
(userContext.apiType === "Mongo" && container.isShellEnabled() && container.isDatabaseNodeOrNoneSelected()) || (userContext.apiType === "Mongo" &&
useNotebook.getState().isShellEnabled &&
selectedNodeState.isDatabaseNodeOrNoneSelected()) ||
userContext.apiType === "Cassandra" userContext.apiType === "Cassandra"
) { ) {
buttons.push(createDivider()); notebookButtons.push(createDivider());
if (userContext.apiType === "Cassandra") { if (userContext.apiType === "Cassandra") {
buttons.push(createOpenCassandraTerminalButton(container)); notebookButtons.push(createOpenCassandraTerminalButton(container));
} else { } else {
buttons.push(createOpenMongoTerminalButton(container)); notebookButtons.push(createOpenMongoTerminalButton(container));
} }
} }
notebookButtons.forEach((btn) => {
if (userContext.features.notebooksTemporarilyDown) {
if (btn.commandButtonLabel.indexOf("Cassandra") !== -1) {
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.cassandraShellTemporarilyDownMsg);
} else if (btn.commandButtonLabel.indexOf("Mongo") !== -1) {
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.mongoShellTemporarilyDownMsg);
} else { } else {
if (!container.isRunningOnNationalCloud()) { applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg);
}
}
buttons.push(btn);
});
} else {
if (!isRunningOnNationalCloud() && !userContext.features.notebooksTemporarilyDown) {
buttons.push(createDivider());
buttons.push(createEnableNotebooksButton(container)); buttons.push(createEnableNotebooksButton(container));
} }
} }
if (!container.isDatabaseNodeOrNoneSelected()) { if (!selectedNodeState.isDatabaseNodeOrNoneSelected()) {
const isQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin"; const isQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
if (isQuerySupported) { if (isQuerySupported) {
buttons.push(createDivider()); buttons.push(createDivider());
const newSqlQueryBtn = createNewSQLQueryButton(container); const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState);
buttons.push(newSqlQueryBtn); buttons.push(newSqlQueryBtn);
} }
if (isQuerySupported && container.selectedNode() && container.findSelectedCollection()) { if (isQuerySupported && selectedNodeState.findSelectedCollection()) {
const openQueryBtn = createOpenQueryButton(container); const openQueryBtn = createOpenQueryButton(container);
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton(container)]; openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton()];
buttons.push(openQueryBtn); buttons.push(openQueryBtn);
} }
@@ -105,16 +136,16 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
iconSrc: AddStoredProcedureIcon, iconSrc: AddStoredProcedureIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection); selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
}, },
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(), disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
}; };
newStoredProcedureBtn.children = createScriptCommandButtons(container); newStoredProcedureBtn.children = createScriptCommandButtons(selectedNodeState);
buttons.push(newStoredProcedureBtn); buttons.push(newStoredProcedureBtn);
} }
} }
@@ -122,18 +153,23 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
return buttons; return buttons;
} }
export function createContextCommandBarButtons(container: Explorer): CommandButtonComponentProps[] { export function createContextCommandBarButtons(
container: Explorer,
selectedNodeState: SelectedNodeState
): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = []; const buttons: CommandButtonComponentProps[] = [];
if (!container.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") { if (!selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") {
const label = container.isShellEnabled() ? "Open Mongo Shell" : "New Shell"; const label = useNotebook.getState().isShellEnabled ? "Open Mongo Shell" : "New Shell";
const newMongoShellBtn: CommandButtonComponentProps = { const newMongoShellBtn: CommandButtonComponentProps = {
iconSrc: HostedTerminalIcon, iconSrc: HostedTerminalIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
if (container.isShellEnabled()) { if (useNotebook.getState().isShellEnabled) {
if (!userContext.features.notebooksTemporarilyDown) {
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo); container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
}
} else { } else {
selectedCollection && selectedCollection.onNewMongoShellClick(); selectedCollection && selectedCollection.onNewMongoShellClick();
} }
@@ -141,7 +177,13 @@ export function createContextCommandBarButtons(container: Explorer): CommandButt
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo", tooltipText:
useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown
? Constants.Notebook.mongoShellTemporarilyDownMsg
: undefined,
disabled:
(selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") ||
(useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown),
}; };
buttons.push(newMongoShellBtn); buttons.push(newMongoShellBtn);
} }
@@ -154,7 +196,7 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
{ {
iconSrc: SettingsIcon, iconSrc: SettingsIcon,
iconAlt: "Settings", iconAlt: "Settings",
onCommandClick: container.openSettingPane, onCommandClick: () => useSidePanel.getState().openSidePanel("Settings", <SettingsPane />),
commandButtonLabel: undefined, commandButtonLabel: undefined,
ariaLabel: "Settings", ariaLabel: "Settings",
tooltipText: "Settings", tooltipText: "Settings",
@@ -163,7 +205,10 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
}, },
]; ];
if (container.isHostedDataExplorerEnabled()) { const showOpenFullScreen =
configContext.platform === Platform.Portal && !isRunningOnNationalCloud() && userContext.apiType !== "Gremlin";
if (showOpenFullScreen) {
const label = "Open Full Screen"; const label = "Open Full Screen";
const fullScreenButton: CommandButtonComponentProps = { const fullScreenButton: CommandButtonComponentProps = {
iconSrc: OpenInTabIcon, iconSrc: OpenInTabIcon,
@@ -175,7 +220,7 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
ariaLabel: label, ariaLabel: label,
tooltipText: label, tooltipText: label,
hasPopup: false, hasPopup: false,
disabled: !container.isHostedDataExplorerEnabled(), disabled: !showOpenFullScreen,
className: "OpenFullScreen", className: "OpenFullScreen",
}; };
buttons.push(fullScreenButton); buttons.push(fullScreenButton);
@@ -234,7 +279,7 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
return undefined; return undefined;
} }
if (container.isServerlessEnabled()) { if (isServerlessAccount()) {
return undefined; return undefined;
} }
@@ -254,7 +299,7 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
onCommandClick: () => container.openEnableSynapseLinkDialog(), onCommandClick: () => container.openEnableSynapseLinkDialog(),
commandButtonLabel: label, commandButtonLabel: label,
hasPopup: false, hasPopup: false,
disabled: container.isSynapseLinkUpdating(), disabled: useNotebook.getState().isSynapseLinkUpdating,
ariaLabel: label, ariaLabel: label,
}; };
} }
@@ -264,29 +309,28 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps {
return { return {
iconSrc: AddDatabaseIcon, iconSrc: AddDatabaseIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () =>
container.openAddDatabasePane(); useSidePanel.getState().openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={container} />),
},
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
}; };
} }
function createNewSQLQueryButton(container: Explorer): CommandButtonComponentProps { function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandButtonComponentProps {
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") { if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
const label = "New SQL Query"; const label = "New SQL Query";
return { return {
iconSrc: AddSqlQueryIcon, iconSrc: AddSqlQueryIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection); selectedCollection && selectedCollection.onNewQueryClick(selectedCollection);
}, },
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(), disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
}; };
} else if (userContext.apiType === "Mongo") { } else if (userContext.apiType === "Mongo") {
const label = "New Query"; const label = "New Query";
@@ -294,23 +338,24 @@ function createNewSQLQueryButton(container: Explorer): CommandButtonComponentPro
iconSrc: AddSqlQueryIcon, iconSrc: AddSqlQueryIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection); selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection);
}, },
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(), disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
}; };
} }
return undefined; return undefined;
} }
export function createScriptCommandButtons(container: Explorer): CommandButtonComponentProps[] { export function createScriptCommandButtons(selectedNodeState: SelectedNodeState): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = []; const buttons: CommandButtonComponentProps[] = [];
const shouldEnableScriptsCommands: boolean = !container.isDatabaseNodeOrNoneSelected() && areScriptsSupported(); const shouldEnableScriptsCommands: boolean =
!selectedNodeState.isDatabaseNodeOrNoneSelected() && areScriptsSupported();
if (shouldEnableScriptsCommands) { if (shouldEnableScriptsCommands) {
const label = "New Stored Procedure"; const label = "New Stored Procedure";
@@ -318,13 +363,13 @@ export function createScriptCommandButtons(container: Explorer): CommandButtonCo
iconSrc: AddStoredProcedureIcon, iconSrc: AddStoredProcedureIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection); selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
}, },
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(), disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
}; };
buttons.push(newStoredProcedureBtn); buttons.push(newStoredProcedureBtn);
} }
@@ -335,13 +380,13 @@ export function createScriptCommandButtons(container: Explorer): CommandButtonCo
iconSrc: AddUdfIcon, iconSrc: AddUdfIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection); selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection);
}, },
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(), disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
}; };
buttons.push(newUserDefinedFunctionBtn); buttons.push(newUserDefinedFunctionBtn);
} }
@@ -352,13 +397,13 @@ export function createScriptCommandButtons(container: Explorer): CommandButtonCo
iconSrc: AddTriggerIcon, iconSrc: AddTriggerIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection); selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection);
}, },
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(), disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
}; };
buttons.push(newTriggerBtn); buttons.push(newTriggerBtn);
} }
@@ -366,6 +411,13 @@ export function createScriptCommandButtons(container: Explorer): CommandButtonCo
return buttons; return buttons;
} }
function applyNotebooksTemporarilyDownStyle(buttonProps: CommandButtonComponentProps, tooltip: string): void {
if (!buttonProps.isDivider) {
buttonProps.disabled = true;
buttonProps.tooltipText = tooltip;
}
}
function createNewNotebookButton(container: Explorer): CommandButtonComponentProps { function createNewNotebookButton(container: Explorer): CommandButtonComponentProps {
const label = "New Notebook"; const label = "New Notebook";
return { return {
@@ -397,7 +449,8 @@ function createOpenQueryButton(container: Explorer): CommandButtonComponentProps
return { return {
iconSrc: BrowseQueriesIcon, iconSrc: BrowseQueriesIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => container.openBrowseQueriesPanel(), onCommandClick: () =>
useSidePanel.getState().openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={container} />),
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
@@ -405,12 +458,12 @@ function createOpenQueryButton(container: Explorer): CommandButtonComponentProps
}; };
} }
function createOpenQueryFromDiskButton(container: Explorer): CommandButtonComponentProps { function createOpenQueryFromDiskButton(): CommandButtonComponentProps {
const label = "Open Query From Disk"; const label = "Open Query From Disk";
return { return {
iconSrc: OpenQueryFromDiskIcon, iconSrc: OpenQueryFromDiskIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => useSidePanel.getState().openSidePanel("Load Query", <LoadQueryPane explorer={container} />), onCommandClick: () => useSidePanel.getState().openSidePanel("Load Query", <LoadQueryPane />),
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
@@ -430,12 +483,18 @@ function createEnableNotebooksButton(container: Explorer): CommandButtonComponen
return { return {
iconSrc: EnableNotebooksIcon, iconSrc: EnableNotebooksIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => container.openSetupNotebooksPanel(label, description), onCommandClick: () =>
useSidePanel
.getState()
.openSidePanel(
label,
<SetupNoteBooksPanel explorer={container} panelTitle={label} panelDescription={description} />
),
commandButtonLabel: label, commandButtonLabel: label,
hasPopup: false, hasPopup: false,
disabled: !container.isNotebooksEnabledForAccount(), disabled: !useNotebook.getState().isNotebooksEnabledForAccount,
ariaLabel: label, ariaLabel: label,
tooltipText: container.isNotebooksEnabledForAccount() ? "" : tooltip, tooltipText: useNotebook.getState().isNotebooksEnabledForAccount ? "" : tooltip,
}; };
} }
@@ -459,15 +518,21 @@ function createOpenMongoTerminalButton(container: Explorer): CommandButtonCompon
const title = "Set up workspace"; const title = "Set up workspace";
const description = const description =
"Looks like you have not created a workspace for this account. To proceed and start using features including mongo shell and notebook, we will need to create a default workspace in this account."; "Looks like you have not created a workspace for this account. To proceed and start using features including mongo shell and notebook, we will need to create a default workspace in this account.";
const disableButton = !container.isNotebooksEnabledForAccount() && !container.isNotebookEnabled(); const disableButton =
!useNotebook.getState().isNotebooksEnabledForAccount && !useNotebook.getState().isNotebookEnabled;
return { return {
iconSrc: HostedTerminalIcon, iconSrc: HostedTerminalIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
if (container.isNotebookEnabled()) { if (useNotebook.getState().isNotebookEnabled) {
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo); container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
} else { } else {
container.openSetupNotebooksPanel(title, description); useSidePanel
.getState()
.openSidePanel(
title,
<SetupNoteBooksPanel explorer={container} panelTitle={title} panelDescription={description} />
);
} }
}, },
commandButtonLabel: label, commandButtonLabel: label,
@@ -485,15 +550,21 @@ function createOpenCassandraTerminalButton(container: Explorer): CommandButtonCo
const title = "Set up workspace"; const title = "Set up workspace";
const description = const description =
"Looks like you have not created a workspace for this account. To proceed and start using features including cassandra shell and notebook, we will need to create a default workspace in this account."; "Looks like you have not created a workspace for this account. To proceed and start using features including cassandra shell and notebook, we will need to create a default workspace in this account.";
const disableButton = !container.isNotebooksEnabledForAccount() && !container.isNotebookEnabled(); const disableButton =
!useNotebook.getState().isNotebooksEnabledForAccount && !useNotebook.getState().isNotebookEnabled;
return { return {
iconSrc: HostedTerminalIcon, iconSrc: HostedTerminalIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
if (container.isNotebookEnabled()) { if (useNotebook.getState().isNotebookEnabled) {
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra); container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
} else { } else {
container.openSetupNotebooksPanel(title, description); useSidePanel
.getState()
.openSidePanel(
title,
<SetupNoteBooksPanel explorer={container} panelTitle={title} panelDescription={description} />
);
} }
}, },
commandButtonLabel: label, commandButtonLabel: label,
@@ -520,10 +591,21 @@ function createNotebookWorkspaceResetButton(container: Explorer): CommandButtonC
function createManageGitHubAccountButton(container: Explorer): CommandButtonComponentProps { function createManageGitHubAccountButton(container: Explorer): CommandButtonComponentProps {
const connectedToGitHub: boolean = container.notebookManager?.gitHubOAuthService.isLoggedIn(); const connectedToGitHub: boolean = container.notebookManager?.gitHubOAuthService.isLoggedIn();
const label = connectedToGitHub ? "Manage GitHub settings" : "Connect to GitHub"; const label = connectedToGitHub ? "Manage GitHub settings" : "Connect to GitHub";
const junoClient = new JunoClient();
return { return {
iconSrc: GitHubIcon, iconSrc: GitHubIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => container.openGitHubReposPanel(label), onCommandClick: () =>
useSidePanel
.getState()
.openSidePanel(
label,
<GitHubReposPanel
explorer={container}
gitHubClientProp={container.notebookManager.gitHubClient}
junoClientProp={junoClient}
/>
),
commandButtonLabel: label, commandButtonLabel: label,
hasPopup: false, hasPopup: false,
disabled: false, disabled: false,
@@ -531,19 +613,25 @@ function createManageGitHubAccountButton(container: Explorer): CommandButtonComp
}; };
} }
function createStaticCommandBarButtonsForResourceToken(container: Explorer): CommandButtonComponentProps[] { function createStaticCommandBarButtonsForResourceToken(
const newSqlQueryBtn = createNewSQLQueryButton(container); container: Explorer,
selectedNodeState: SelectedNodeState
): CommandButtonComponentProps[] {
const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState);
const openQueryBtn = createOpenQueryButton(container); const openQueryBtn = createOpenQueryButton(container);
newSqlQueryBtn.disabled = !container.isResourceTokenCollectionNodeSelected(); const resourceTokenCollection: ViewModels.CollectionBase = useDatabases.getState().resourceTokenCollection;
const isResourceTokenCollectionNodeSelected: boolean =
resourceTokenCollection?.id() === selectedNodeState.selectedNode?.id();
newSqlQueryBtn.disabled = !isResourceTokenCollectionNodeSelected;
newSqlQueryBtn.onCommandClick = () => { newSqlQueryBtn.onCommandClick = () => {
const resourceTokenCollection: ViewModels.CollectionBase = container.resourceTokenCollection(); const resourceTokenCollection: ViewModels.CollectionBase = useDatabases.getState().resourceTokenCollection;
resourceTokenCollection && resourceTokenCollection.onNewQueryClick(resourceTokenCollection, undefined); resourceTokenCollection && resourceTokenCollection.onNewQueryClick(resourceTokenCollection, undefined);
}; };
openQueryBtn.disabled = !container.isResourceTokenCollectionNodeSelected(); openQueryBtn.disabled = !isResourceTokenCollectionNodeSelected;
if (!openQueryBtn.disabled) { if (!openQueryBtn.disabled) {
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton(container)]; openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton()];
} }
return [newSqlQueryBtn, openQueryBtn]; return [newSqlQueryBtn, openQueryBtn];

View File

@@ -6,16 +6,14 @@ import {
IDropdownOption, IDropdownOption,
IDropdownStyles, IDropdownStyles,
} from "@fluentui/react"; } from "@fluentui/react";
import { Observable } from "knockout";
import * as React from "react"; import * as React from "react";
import _ from "underscore"; import _ from "underscore";
import ChevronDownIcon from "../../../../images/Chevron_down.svg"; import ChevronDownIcon from "../../../../images/Chevron_down.svg";
import { StyleConstants } from "../../../Common/Constants"; import { StyleConstants } from "../../../Common/Constants";
import { MemoryUsageInfo } from "../../../Contracts/DataModels";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import { MemoryTrackerComponent } from "./MemoryTrackerComponent"; import { MemoryTracker } from "./MemoryTrackerComponent";
/** /**
* Convert our NavbarButtonConfig to UI Fabric buttons * Convert our NavbarButtonConfig to UI Fabric buttons
@@ -24,6 +22,13 @@ import { MemoryTrackerComponent } from "./MemoryTrackerComponent";
export const convertButton = (btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] => { export const convertButton = (btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] => {
const buttonHeightPx = StyleConstants.CommandBarButtonHeight; const buttonHeightPx = StyleConstants.CommandBarButtonHeight;
const getFilter = (isDisabled: boolean): string => {
if (isDisabled) {
return StyleConstants.GrayScale;
}
return undefined;
};
return btns return btns
.filter((btn) => btn) .filter((btn) => btn)
.map( .map(
@@ -39,6 +44,7 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
style: { style: {
width: StyleConstants.CommandBarIconWidth, // 16 width: StyleConstants.CommandBarIconWidth, // 16
alignSelf: btn.iconName ? "baseline" : undefined, alignSelf: btn.iconName ? "baseline" : undefined,
filter: getFilter(btn.disabled),
}, },
imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined, imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined,
iconName: btn.iconName, iconName: btn.iconName,
@@ -125,8 +131,12 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
width: 12, width: 12,
paddingLeft: 1, paddingLeft: 1,
paddingTop: 6, paddingTop: 6,
filter: getFilter(btn.disabled),
},
imageProps: {
src: ChevronDownIcon,
alt: btn.iconAlt,
}, },
imageProps: { src: ChevronDownIcon, alt: btn.iconAlt },
}; };
} }
@@ -185,12 +195,9 @@ export const createDivider = (key: string): ICommandBarItemProps => {
}; };
}; };
export const createMemoryTracker = ( export const createMemoryTracker = (key: string): ICommandBarItemProps => {
key: string,
memoryUsageInfo: Observable<MemoryUsageInfo>
): ICommandBarItemProps => {
return { return {
key, key,
onRender: () => <MemoryTrackerComponent memoryUsageInfo={memoryUsageInfo} />, onRender: () => <MemoryTracker />,
}; };
}; };

View File

@@ -1,27 +1,9 @@
import { ProgressIndicator, Spinner, SpinnerSize, Stack } from "@fluentui/react"; import { ProgressIndicator, Spinner, SpinnerSize, Stack } from "@fluentui/react";
import { Observable, Subscription } from "knockout";
import * as React from "react"; import * as React from "react";
import { MemoryUsageInfo } from "../../../Contracts/DataModels"; import { useNotebook } from "../../Notebook/useNotebook";
interface MemoryTrackerProps { export const MemoryTracker: React.FC = (): JSX.Element => {
memoryUsageInfo: Observable<MemoryUsageInfo>; const memoryUsageInfo = useNotebook((state) => state.memoryUsageInfo);
}
export class MemoryTrackerComponent extends React.Component<MemoryTrackerProps> {
private memoryUsageInfoSubscription: Subscription;
public componentDidMount(): void {
this.memoryUsageInfoSubscription = this.props.memoryUsageInfo.subscribe(() => {
this.forceUpdate();
});
}
public componentWillUnmount(): void {
this.memoryUsageInfoSubscription && this.memoryUsageInfoSubscription.dispose();
}
public render(): JSX.Element {
const memoryUsageInfo: MemoryUsageInfo = this.props.memoryUsageInfo();
if (!memoryUsageInfo) { if (!memoryUsageInfo) {
return ( return (
<Stack className="memoryTrackerContainer" horizontal> <Stack className="memoryTrackerContainer" horizontal>
@@ -44,5 +26,4 @@ export class MemoryTrackerComponent extends React.Component<MemoryTrackerProps>
/> />
</Stack> </Stack>
); );
} };
}

View File

@@ -1,33 +0,0 @@
/**
* React component for control bar
*/
import * as React from "react";
import {
CommandButtonComponent,
CommandButtonComponentProps,
} from "../../Controls/CommandButton/CommandButtonComponent";
export interface ControlBarComponentProps {
buttons: CommandButtonComponentProps[];
}
export class ControlBarComponent extends React.Component<ControlBarComponentProps> {
private static renderButtons(commandButtonOptions: CommandButtonComponentProps[]): JSX.Element[] {
return commandButtonOptions.map(
(btn: CommandButtonComponentProps, index: number): JSX.Element => {
// Remove label
btn.commandButtonLabel = undefined;
return CommandButtonComponent.renderButton(btn, `${index}`);
}
);
}
public render(): JSX.Element {
if (!this.props.buttons || this.props.buttons.length < 1) {
return <React.Fragment />;
}
return <React.Fragment>{ControlBarComponent.renderButtons(this.props.buttons)}</React.Fragment>;
}
}

View File

@@ -1,28 +0,0 @@
/**
* This adapter is responsible to render the React component
* If the component signals a change through the callback passed in the properties, it must render the React component when appropriate
* and update any knockout observables passed from the parent.
*/
import * as ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { ControlBarComponent } from "./ControlBarComponent";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
export class ControlBarComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
constructor(private buttons: ko.ObservableArray<CommandButtonComponentProps>) {
this.buttons.subscribe(() => this.forceRender());
this.parameters = ko.observable<number>(Date.now());
}
public renderComponent(): JSX.Element {
return <ControlBarComponent buttons={this.buttons()} />;
}
public forceRender(): void {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
}

View File

@@ -0,0 +1,16 @@
/**
* Interface for the data/content that will be recorded
*/
export interface ConsoleData {
type: ConsoleDataType;
date: string;
message: string;
id?: string;
}
export enum ConsoleDataType {
Info = 0,
Error = 1,
InProgress = 2,
}

View File

@@ -1,10 +1,7 @@
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import React from "react"; import React from "react";
import { import { ConsoleDataType } from "./ConsoleData";
ConsoleDataType, import { NotificationConsoleComponent, NotificationConsoleComponentProps } from "./NotificationConsoleComponent";
NotificationConsoleComponent,
NotificationConsoleComponentProps,
} from "./NotificationConsoleComponent";
describe("NotificationConsoleComponent", () => { describe("NotificationConsoleComponent", () => {
const createBlankProps = (): NotificationConsoleComponentProps => { const createBlankProps = (): NotificationConsoleComponentProps => {

View File

@@ -6,7 +6,7 @@ import { Dropdown, IDropdownOption } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import AnimateHeight from "react-animate-height"; import AnimateHeight from "react-animate-height";
import LoaderIcon from "../../../../images/circular_loader_black_16x16.gif"; import LoaderIcon from "../../../../images/circular_loader_black_16x16.gif";
import ClearIcon from "../../../../images/Clear.svg"; import ClearIcon from "../../../../images/Clear-1.svg";
import ErrorBlackIcon from "../../../../images/error_black.svg"; import ErrorBlackIcon from "../../../../images/error_black.svg";
import ErrorRedIcon from "../../../../images/error_red.svg"; import ErrorRedIcon from "../../../../images/error_red.svg";
import infoBubbleIcon from "../../../../images/info-bubble-9x9.svg"; import infoBubbleIcon from "../../../../images/info-bubble-9x9.svg";
@@ -17,25 +17,7 @@ import ChevronUpIcon from "../../../../images/QueryBuilder/CollapseChevronUp_16x
import { ClientDefaults, KeyCodes } from "../../../Common/Constants"; import { ClientDefaults, KeyCodes } from "../../../Common/Constants";
import { useNotificationConsole } from "../../../hooks/useNotificationConsole"; import { useNotificationConsole } from "../../../hooks/useNotificationConsole";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
import { ConsoleData, ConsoleDataType } from "./ConsoleData";
/**
* Log levels
*/
export enum ConsoleDataType {
Info = 0,
Error = 1,
InProgress = 2,
}
/**
* Interface for the data/content that will be recorded
*/
export interface ConsoleData {
type: ConsoleDataType;
date: string;
message: string;
id?: string;
}
export interface NotificationConsoleComponentProps { export interface NotificationConsoleComponentProps {
isConsoleExpanded: boolean; isConsoleExpanded: boolean;
@@ -323,14 +305,13 @@ const PrPreview = (props: { pr: string }) => {
); );
}; };
export const NotificationConsole: React.FC< export const NotificationConsole: React.FC = () => {
Pick<NotificationConsoleComponentProps, "consoleData" | "inProgressConsoleDataIdToBeDeleted">
> = ({
consoleData,
inProgressConsoleDataIdToBeDeleted,
}: Pick<NotificationConsoleComponentProps, "consoleData" | "inProgressConsoleDataIdToBeDeleted">) => {
const setIsExpanded = useNotificationConsole((state) => state.setIsExpanded); const setIsExpanded = useNotificationConsole((state) => state.setIsExpanded);
const isExpanded = useNotificationConsole((state) => state.isExpanded); const isExpanded = useNotificationConsole((state) => state.isExpanded);
const consoleData = useNotificationConsole((state) => state.consoleData);
const inProgressConsoleDataIdToBeDeleted = useNotificationConsole(
(state) => state.inProgressConsoleDataIdToBeDeleted
);
// TODO Refactor NotificationConsoleComponent into a functional component and remove this wrapper // TODO Refactor NotificationConsoleComponent into a functional component and remove this wrapper
// This component only exists so we can use hooks and pass them down to a non-functional component // This component only exists so we can use hooks and pass them down to a non-functional component
return ( return (

View File

@@ -21,7 +21,7 @@ import {
makeStateRecord, makeStateRecord,
makeTransformsRecord, makeTransformsRecord,
} from "@nteract/core"; } from "@nteract/core";
import { configOption, createConfigCollection, defineConfigOption } from "@nteract/mythic-configuration"; import { configOption, defineConfigOption } from "@nteract/mythic-configuration";
import { Media } from "@nteract/outputs"; import { Media } from "@nteract/outputs";
import TransformVDOM from "@nteract/transform-vdom"; import TransformVDOM from "@nteract/transform-vdom";
import * as Immutable from "immutable"; import * as Immutable from "immutable";
@@ -242,22 +242,27 @@ export class NotebookClientV2 {
); );
// Additional configuration // Additional configuration
this.store.dispatch(configOption("editorType").action(params.cellEditorType ?? "monaco")); this.store.dispatch(configOption("editorType").action(params.cellEditorType ?? "codemirror"));
this.store.dispatch( this.store.dispatch(
configOption("autoSaveInterval").action(params.autoSaveInterval ?? Constants.Notebook.autoSaveIntervalMs) configOption("autoSaveInterval").action(params.autoSaveInterval ?? Constants.Notebook.autoSaveIntervalMs)
); );
createConfigCollection({ this.store.dispatch(configOption("codeMirror.lineNumbers").action(true));
key: "monaco",
}); const readOnlyConfigOption = configOption("codeMirror.readOnly");
const readOnlyValue = params.isReadOnly ? "nocursor" : undefined;
if (!readOnlyConfigOption) {
defineConfigOption({ defineConfigOption({
label: "Show Line numbers", label: "Read-only",
key: "monaco.lineNumbers", key: "codeMirror.readOnly",
values: [ values: [
{ label: "Yes", value: true }, { label: "Read-Only", value: "nocursor" },
{ label: "No", value: false }, { label: "Not read-only", value: undefined },
], ],
defaultValue: true, defaultValue: readOnlyValue,
}); });
} else {
this.store.dispatch(readOnlyConfigOption.action(readOnlyValue));
}
} }
/** /**

View File

@@ -277,6 +277,10 @@ export class NotebookComponentBootstrapper {
return selectors.notebook.isDirty(content.model as Immutable.RecordOf<DocumentRecordProps>); return selectors.notebook.isDirty(content.model as Immutable.RecordOf<DocumentRecordProps>);
} }
public isNotebookUntrusted(): boolean {
return NotebookUtil.isNotebookUntrusted(this.getStore().getState(), this.contentRef);
}
/** /**
* For display purposes, only return non-killed kernels * For display purposes, only return non-killed kernels
*/ */

View File

@@ -1,12 +1,14 @@
import * as React from "react";
import { AppState, ContentRef, selectors } from "@nteract/core"; import { AppState, ContentRef, selectors } from "@nteract/core";
import * as React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { NotebookUtil } from "../NotebookUtil";
import * as NteractUtil from "../NTeractUtil"; import * as NteractUtil from "../NTeractUtil";
interface VirtualCommandBarComponentProps { interface VirtualCommandBarComponentProps {
kernelSpecName: string; kernelSpecName: string;
kernelStatus: string; kernelStatus: string;
currentCellType: string; currentCellType: string;
isNotebookUntrusted: boolean;
onRender: () => void; onRender: () => void;
} }
@@ -20,7 +22,8 @@ class VirtualCommandBarComponent extends React.Component<VirtualCommandBarCompon
return ( return (
this.props.kernelStatus !== nextProps.kernelStatus || this.props.kernelStatus !== nextProps.kernelStatus ||
this.props.kernelSpecName !== nextProps.kernelSpecName || this.props.kernelSpecName !== nextProps.kernelSpecName ||
this.props.currentCellType !== nextProps.currentCellType this.props.currentCellType !== nextProps.currentCellType ||
this.props.isNotebookUntrusted !== nextProps.isNotebookUntrusted
); );
} }
@@ -50,6 +53,7 @@ const makeMapStateToProps = (
kernelStatus, kernelStatus,
kernelSpecName, kernelSpecName,
currentCellType, currentCellType,
isNotebookUntrusted: NotebookUtil.isNotebookUntrusted(state, contentRef),
} as VirtualCommandBarComponentProps; } as VirtualCommandBarComponentProps;
} }
@@ -69,6 +73,7 @@ const makeMapStateToProps = (
kernelStatus, kernelStatus,
kernelSpecName, kernelSpecName,
currentCellType, currentCellType,
isNotebookUntrusted: NotebookUtil.isNotebookUntrusted(state, contentRef),
onRender: initialProps.onRender, onRender: initialProps.onRender,
}; };
}; };

View File

@@ -2,7 +2,6 @@ import { AppState, ContentRef, selectors } from "@nteract/core";
import * as React from "react"; import * as React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import styled from "styled-components"; import styled from "styled-components";
import NotebookRenderer from "../../../NotebookRenderer/NotebookRenderer"; import NotebookRenderer from "../../../NotebookRenderer/NotebookRenderer";
import * as TextFile from "./text-file"; import * as TextFile from "./text-file";
@@ -32,14 +31,14 @@ interface FileProps {
export class File extends React.PureComponent<FileProps> { export class File extends React.PureComponent<FileProps> {
getChoice = () => { getChoice = () => {
let choice = null; let choice;
// notebooks don't report a mimetype so we'll use the content.type // notebooks don't report a mimetype so we'll use the content.type
if (this.props.type === "notebook") { if (this.props.type === "notebook") {
choice = <NotebookRenderer contentRef={this.props.contentRef} />; choice = <NotebookRenderer contentRef={this.props.contentRef} />;
} else if (this.props.type === "dummy") { } else if (this.props.type === "dummy") {
choice = null; choice = undefined;
} else if (this.props.mimetype == null || !TextFile.handles(this.props.mimetype)) { } else if (this.props.mimetype === undefined || !TextFile.handles(this.props.mimetype)) {
// This should not happen as we intercept mimetype upstream, but just in case // This should not happen as we intercept mimetype upstream, but just in case
choice = ( choice = (
<PaddedContainer> <PaddedContainer>

View File

@@ -1,10 +1,10 @@
import * as StringUtils from "../../../../../Utils/StringUtils";
import { actions, AppState, ContentRef, selectors } from "@nteract/core"; import { actions, AppState, ContentRef, selectors } from "@nteract/core";
import { IMonacoProps as MonacoEditorProps } from "@nteract/monaco-editor"; import { IMonacoProps as MonacoEditorProps } from "@nteract/monaco-editor";
import * as React from "react"; import * as React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Dispatch } from "redux"; import { Dispatch } from "redux";
import styled from "styled-components"; import styled from "styled-components";
import * as StringUtils from "../../../../../Utils/StringUtils";
const EditorContainer = styled.div` const EditorContainer = styled.div`
position: absolute; position: absolute;
@@ -18,7 +18,7 @@ const EditorContainer = styled.div`
`; `;
interface MappedStateProps { interface MappedStateProps {
mimetype: string; mimetype: string | null;
text: string; text: string;
contentRef: ContentRef; contentRef: ContentRef;
theme?: "light" | "dark"; theme?: "light" | "dark";
@@ -37,7 +37,7 @@ interface TextFileState {
class EditorPlaceholder extends React.PureComponent<MonacoEditorProps> { class EditorPlaceholder extends React.PureComponent<MonacoEditorProps> {
render(): JSX.Element { render(): JSX.Element {
// TODO: Show a little blocky placeholder // TODO: Show a little blocky placeholder
return null; return <div />;
} }
} }
@@ -83,7 +83,7 @@ interface InitialProps {
} }
function makeMapStateToTextFileProps( function makeMapStateToTextFileProps(
initialState: AppState, _initialState: AppState,
initialProps: InitialProps initialProps: InitialProps
): (state: AppState) => MappedStateProps { ): (state: AppState) => MappedStateProps {
const { contentRef } = initialProps; const { contentRef } = initialProps;
@@ -98,7 +98,7 @@ function makeMapStateToTextFileProps(
return { return {
contentRef, contentRef,
mimetype: content.mimetype != null ? content.mimetype : "text/plain", mimetype: content.mimetype !== undefined ? content.mimetype : "text/plain",
text, text,
}; };
}; };
@@ -106,7 +106,7 @@ function makeMapStateToTextFileProps(
} }
const makeMapDispatchToTextFileProps = ( const makeMapDispatchToTextFileProps = (
initialDispatch: Dispatch, _initialDispatch: Dispatch,
initialProps: InitialProps initialProps: InitialProps
): ((dispatch: Dispatch) => MappedDispatchProps) => { ): ((dispatch: Dispatch) => MappedDispatchProps) => {
const { contentRef } = initialProps; const { contentRef } = initialProps;

View File

@@ -34,9 +34,11 @@ import {
import { webSocket } from "rxjs/webSocket"; import { webSocket } from "rxjs/webSocket";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
import { Areas } from "../../../Common/Constants"; import { Areas } from "../../../Common/Constants";
import { useTabs } from "../../../hooks/useTabs";
import { Action as TelemetryAction, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action as TelemetryAction, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { logConsoleError, logConsoleInfo } from "../../../Utils/NotificationConsoleUtils"; import { logConsoleError, logConsoleInfo } from "../../../Utils/NotificationConsoleUtils";
import { useDialog } from "../../Controls/Dialog";
import * as FileSystemUtil from "../FileSystemUtil"; import * as FileSystemUtil from "../FileSystemUtil";
import * as cdbActions from "../NotebookComponent/actions"; import * as cdbActions from "../NotebookComponent/actions";
import { NotebookUtil } from "../NotebookUtil"; import { NotebookUtil } from "../NotebookUtil";
@@ -685,10 +687,8 @@ const handleKernelConnectionLostEpic = (
logConsoleError(msg); logConsoleError(msg);
logFailureToTelemetry(state, "Kernel restart error", msg); logFailureToTelemetry(state, "Kernel restart error", msg);
const explorer = window.dataExplorer; useDialog.getState().showOkModalDialog("kernel restarts", msg);
if (explorer) {
explorer.showOkModalDialog("kernel restarts", msg);
}
return of(EMPTY); return of(EMPTY);
} }
@@ -772,15 +772,16 @@ const closeUnsupportedMimetypesEpic = (
ofType(actions.FETCH_CONTENT_FULFILLED), ofType(actions.FETCH_CONTENT_FULFILLED),
mergeMap((action) => { mergeMap((action) => {
const mimetype = action.payload.model.mimetype; const mimetype = action.payload.model.mimetype;
const explorer = window.dataExplorer; if (!TextFile.handles(mimetype)) {
if (explorer && !TextFile.handles(mimetype)) {
const filepath = action.payload.filepath; const filepath = action.payload.filepath;
// Close tab and show error message // Close tab and show error message
explorer.tabsManager.closeTabsByComparator( useTabs
.getState()
.closeTabsByComparator(
(tab: any) => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath) (tab: any) => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
); );
const msg = `${filepath} cannot be rendered. Please download the file, in order to view it outside of Data Explorer.`; const msg = `${filepath} cannot be rendered. Please download the file, in order to view it outside of Data Explorer.`;
explorer.showOkModalDialog("File cannot be rendered", msg); useDialog.getState().showOkModalDialog("File cannot be rendered", msg);
logConsoleError(msg); logConsoleError(msg);
} }
return EMPTY; return EMPTY;
@@ -800,17 +801,16 @@ const closeContentFailedToFetchEpic = (
return action$.pipe( return action$.pipe(
ofType(actions.FETCH_CONTENT_FAILED), ofType(actions.FETCH_CONTENT_FAILED),
mergeMap((action) => { mergeMap((action) => {
const explorer = window.dataExplorer;
if (explorer) {
const filepath = action.payload.filepath; const filepath = action.payload.filepath;
// Close tab and show error message // Close tab and show error message
explorer.tabsManager.closeTabsByComparator( useTabs
.getState()
.closeTabsByComparator(
(tab: any) => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath) (tab: any) => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
); );
const msg = `Failed to load file: ${filepath}.`; const msg = `Failed to load file: ${filepath}.`;
explorer.showOkModalDialog("Failure to load", msg); useDialog.getState().showOkModalDialog("Failure to load", msg);
logConsoleError(msg); logConsoleError(msg);
}
return EMPTY; return EMPTY;
}) })
); );

View File

@@ -6,26 +6,28 @@ import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
import * as Logger from "../../Common/Logger"; import * as Logger from "../../Common/Logger";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { createOrUpdate, destroy } from "../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { useNotebook } from "./useNotebook";
export class NotebookContainerClient { export class NotebookContainerClient {
private clearReconnectionAttemptMessage? = () => {}; private clearReconnectionAttemptMessage? = () => {};
private isResettingWorkspace: boolean; private isResettingWorkspace: boolean;
constructor( constructor(private onConnectionLost: () => void) {
private notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>, const notebookServerInfo = useNotebook.getState().notebookServerInfo;
private onConnectionLost: () => void, if (notebookServerInfo?.notebookServerEndpoint) {
private onMemoryUsageInfoUpdate: (update: DataModels.MemoryUsageInfo) => void
) {
if (notebookServerInfo() && notebookServerInfo().notebookServerEndpoint) {
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs); this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
} else { } else {
const subscription = notebookServerInfo.subscribe((newServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => { const unsub = useNotebook.subscribe(
if (newServerInfo && newServerInfo.notebookServerEndpoint) { (newServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => {
if (newServerInfo?.notebookServerEndpoint) {
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs); this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
} }
subscription.dispose(); unsub();
}); },
(state) => state.notebookServerInfo
);
} }
} }
@@ -35,13 +37,14 @@ export class NotebookContainerClient {
private scheduleHeartbeat(delayMs: number): void { private scheduleHeartbeat(delayMs: number): void {
setTimeout(() => { setTimeout(() => {
this.getMemoryUsage() this.getMemoryUsage()
.then((memoryUsageInfo) => this.onMemoryUsageInfoUpdate(memoryUsageInfo)) .then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo))
.finally(() => this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs)); .finally(() => this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs));
}, delayMs); }, delayMs);
} }
private async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> { private async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
if (!this.notebookServerInfo() || !this.notebookServerInfo().notebookServerEndpoint) { const notebookServerInfo = useNotebook.getState().notebookServerInfo;
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
const error = "No server endpoint detected"; const error = "No server endpoint detected";
Logger.logError(error, "NotebookContainerClient/getMemoryUsage"); Logger.logError(error, "NotebookContainerClient/getMemoryUsage");
return Promise.reject(error); return Promise.reject(error);
@@ -97,7 +100,8 @@ export class NotebookContainerClient {
} }
private async _resetWorkspace(): Promise<void> { private async _resetWorkspace(): Promise<void> {
if (!this.notebookServerInfo() || !this.notebookServerInfo().notebookServerEndpoint) { const notebookServerInfo = useNotebook.getState().notebookServerInfo;
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
const error = "No server endpoint detected"; const error = "No server endpoint detected";
Logger.logError(error, "NotebookContainerClient/resetWorkspace"); Logger.logError(error, "NotebookContainerClient/resetWorkspace");
return Promise.reject(error); return Promise.reject(error);
@@ -116,30 +120,28 @@ export class NotebookContainerClient {
} }
private getNotebookServerConfig(): { notebookServerEndpoint: string; authToken: string } { private getNotebookServerConfig(): { notebookServerEndpoint: string; authToken: string } {
let authToken: string, const notebookServerInfo = useNotebook.getState().notebookServerInfo;
notebookServerEndpoint = this.notebookServerInfo().notebookServerEndpoint, const authToken: string = notebookServerInfo.authToken ? `Token ${notebookServerInfo.authToken}` : undefined;
token = this.notebookServerInfo().authToken;
if (token) {
authToken = `Token ${token}`;
}
return { return {
notebookServerEndpoint, notebookServerEndpoint: notebookServerInfo.notebookServerEndpoint,
authToken, authToken,
}; };
} }
private async recreateNotebookWorkspaceAsync(): Promise<void> { private async recreateNotebookWorkspaceAsync(): Promise<void> {
const explorer = window.dataExplorer;
const { databaseAccount } = userContext; const { databaseAccount } = userContext;
if (!databaseAccount?.id) { if (!databaseAccount?.id) {
throw new Error("DataExplorer not initialized"); throw new Error("DataExplorer not initialized");
} }
const notebookWorkspaceManager = explorer.notebookWorkspaceManager;
try { try {
await notebookWorkspaceManager.deleteNotebookWorkspaceAsync(databaseAccount?.id, "default"); await destroy(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, "default");
await notebookWorkspaceManager.createNotebookWorkspaceAsync(databaseAccount?.id, "default"); await createOrUpdate(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
"default"
);
} catch (error) { } catch (error) {
Logger.logError(getErrorMessage(error), "NotebookContainerClient/recreateNotebookWorkspaceAsync"); Logger.logError(getErrorMessage(error), "NotebookContainerClient/recreateNotebookWorkspaceAsync");
return Promise.reject(error); return Promise.reject(error);

View File

@@ -1,24 +1,31 @@
import { stringifyNotebook } from "@nteract/commutable"; import { stringifyNotebook } from "@nteract/commutable";
import { FileType, IContent, IContentProvider, IEmptyContent, ServerConfig } from "@nteract/core"; import { FileType, IContent, IContentProvider, IEmptyContent, ServerConfig } from "@nteract/core";
import { cloneDeep } from "lodash";
import { AjaxResponse } from "rxjs/ajax"; import { AjaxResponse } from "rxjs/ajax";
import * as DataModels from "../../Contracts/DataModels";
import * as StringUtils from "../../Utils/StringUtils"; import * as StringUtils from "../../Utils/StringUtils";
import * as FileSystemUtil from "./FileSystemUtil"; import * as FileSystemUtil from "./FileSystemUtil";
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem"; import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
import { NotebookUtil } from "./NotebookUtil"; import { NotebookUtil } from "./NotebookUtil";
import { useNotebook } from "./useNotebook";
export class NotebookContentClient { export class NotebookContentClient {
constructor( constructor(private contentProvider: IContentProvider) {}
private notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>,
public notebookBasePath: ko.Observable<string>,
private contentProvider: IContentProvider
) {}
/** /**
* This updates the item and points all the children's parent to this item * This updates the item and points all the children's parent to this item
* @param item * @param item
*/ */
public updateItemChildren(item: NotebookContentItem): Promise<void> { public async updateItemChildren(item: NotebookContentItem): Promise<NotebookContentItem> {
const subItems = await this.fetchNotebookFiles(item.path);
const clonedItem = cloneDeep(item);
subItems.forEach((subItem) => (subItem.parent = clonedItem));
clonedItem.children = subItems;
return clonedItem;
}
// TODO: Delete this function when ResourceTreeAdapter is removed.
public async updateItemChildrenInPlace(item: NotebookContentItem): Promise<void> {
return this.fetchNotebookFiles(item.path).then((subItems) => { return this.fetchNotebookFiles(item.path).then((subItems) => {
item.children = subItems; item.children = subItems;
subItems.forEach((subItem) => (subItem.parent = item)); subItems.forEach((subItem) => (subItem.parent = item));
@@ -29,7 +36,7 @@ export class NotebookContentClient {
* *
* @param parent parent folder * @param parent parent folder
*/ */
public createNewNotebookFile(parent: NotebookContentItem): Promise<NotebookContentItem> { public createNewNotebookFile(parent: NotebookContentItem, isGithubTree?: boolean): Promise<NotebookContentItem> {
if (!parent || parent.type !== NotebookContentItemType.Directory) { if (!parent || parent.type !== NotebookContentItemType.Directory) {
throw new Error(`Parent must be a directory: ${parent}`); throw new Error(`Parent must be a directory: ${parent}`);
} }
@@ -50,6 +57,8 @@ export class NotebookContentClient {
const notebookFile = xhr.response; const notebookFile = xhr.response;
const item = NotebookUtil.createNotebookContentItem(notebookFile.name, notebookFile.path, notebookFile.type); const item = NotebookUtil.createNotebookContentItem(notebookFile.name, notebookFile.path, notebookFile.type);
useNotebook.getState().insertNotebookItem(parent, cloneDeep(item), isGithubTree);
// TODO: delete when ResourceTreeAdapter is removed
if (parent.children) { if (parent.children) {
item.parent = parent; item.parent = parent;
parent.children.push(item); parent.children.push(item);
@@ -59,8 +68,11 @@ export class NotebookContentClient {
}); });
} }
public deleteContentItem(item: NotebookContentItem): Promise<void> { public async deleteContentItem(item: NotebookContentItem, isGithubTree?: boolean): Promise<void> {
return this.deleteNotebookFile(item.path).then((path: string) => { const path = await this.deleteNotebookFile(item.path);
useNotebook.getState().deleteNotebookItem(item, isGithubTree);
// TODO: Delete once old resource tree is removed
if (!path || path !== item.path) { if (!path || path !== item.path) {
throw new Error("No path provided"); throw new Error("No path provided");
} }
@@ -70,7 +82,6 @@ export class NotebookContentClient {
const newChildren = item.parent.children.filter((child) => child.path !== path); const newChildren = item.parent.children.filter((child) => child.path !== path);
item.parent.children = newChildren; item.parent.children = newChildren;
} }
});
} }
/** /**
@@ -82,7 +93,8 @@ export class NotebookContentClient {
public async uploadFileAsync( public async uploadFileAsync(
name: string, name: string,
content: string, content: string,
parent: NotebookContentItem parent: NotebookContentItem,
isGithubTree?: boolean
): Promise<NotebookContentItem> { ): Promise<NotebookContentItem> {
if (!parent || parent.type !== NotebookContentItemType.Directory) { if (!parent || parent.type !== NotebookContentItemType.Directory) {
throw new Error(`Parent must be a directory: ${parent}`); throw new Error(`Parent must be a directory: ${parent}`);
@@ -106,6 +118,8 @@ export class NotebookContentClient {
.then((xhr: AjaxResponse) => { .then((xhr: AjaxResponse) => {
const notebookFile = xhr.response; const notebookFile = xhr.response;
const item = NotebookUtil.createNotebookContentItem(notebookFile.name, notebookFile.path, notebookFile.type); const item = NotebookUtil.createNotebookContentItem(notebookFile.name, notebookFile.path, notebookFile.type);
useNotebook.getState().insertNotebookItem(parent, cloneDeep(item), isGithubTree);
// TODO: delete when ResourceTreeAdapter is removed
if (parent.children) { if (parent.children) {
item.parent = parent; item.parent = parent;
parent.children.push(item); parent.children.push(item);
@@ -128,7 +142,11 @@ export class NotebookContentClient {
* @param sourcePath * @param sourcePath
* @param targetName is not prefixed with path * @param targetName is not prefixed with path
*/ */
public renameNotebook(item: NotebookContentItem, targetName: string): Promise<NotebookContentItem> { public renameNotebook(
item: NotebookContentItem,
targetName: string,
isGithubTree?: boolean
): Promise<NotebookContentItem> {
const sourcePath = item.path; const sourcePath = item.path;
// Match extension // Match extension
if (sourcePath.indexOf(".") !== -1) { if (sourcePath.indexOf(".") !== -1) {
@@ -154,6 +172,9 @@ export class NotebookContentClient {
item.name = notebookFile.name; item.name = notebookFile.name;
item.path = notebookFile.path; item.path = notebookFile.path;
item.timestamp = NotebookUtil.getCurrentTimestamp(); item.timestamp = NotebookUtil.getCurrentTimestamp();
useNotebook.getState().updateNotebookItem(item, isGithubTree);
return item; return item;
}); });
} }
@@ -163,7 +184,11 @@ export class NotebookContentClient {
* @param parent * @param parent
* @param newDirectoryName basename of the new directory * @param newDirectoryName basename of the new directory
*/ */
public async createDirectory(parent: NotebookContentItem, newDirectoryName: string): Promise<NotebookContentItem> { public async createDirectory(
parent: NotebookContentItem,
newDirectoryName: string,
isGithubTree?: boolean
): Promise<NotebookContentItem> {
if (parent.type !== NotebookContentItemType.Directory) { if (parent.type !== NotebookContentItemType.Directory) {
throw new Error(`Parent is not a directory: ${parent.path}`); throw new Error(`Parent is not a directory: ${parent.path}`);
} }
@@ -190,8 +215,11 @@ export class NotebookContentClient {
const dir = xhr.response; const dir = xhr.response;
const item = NotebookUtil.createNotebookContentItem(dir.name, dir.path, dir.type); const item = NotebookUtil.createNotebookContentItem(dir.name, dir.path, dir.type);
useNotebook.getState().insertNotebookItem(parent, cloneDeep(item), isGithubTree);
// TODO: delete when ResourceTreeAdapter is removed
item.parent = parent; item.parent = parent;
parent.children?.push(item); parent.children?.push(item);
return item; return item;
}); });
} }
@@ -271,9 +299,10 @@ export class NotebookContentClient {
} }
private getServerConfig(): ServerConfig { private getServerConfig(): ServerConfig {
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
return { return {
endpoint: this.notebookServerInfo().notebookServerEndpoint, endpoint: notebookServerInfo.notebookServerEndpoint,
token: this.notebookServerInfo().authToken, token: notebookServerInfo.authToken,
crossDomain: true, crossDomain: true,
}; };
} }

View File

@@ -4,13 +4,11 @@
import { ImmutableNotebook } from "@nteract/commutable"; import { ImmutableNotebook } from "@nteract/commutable";
import type { IContentProvider } from "@nteract/core"; import type { IContentProvider } from "@nteract/core";
import ko from "knockout";
import React from "react"; import React from "react";
import { contents } from "rx-jupyter"; import { contents } from "rx-jupyter";
import { Areas, HttpStatusCodes } from "../../Common/Constants"; import { Areas, HttpStatusCodes } from "../../Common/Constants";
import { getErrorMessage } from "../../Common/ErrorHandlingUtils"; import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
import * as Logger from "../../Common/Logger"; import * as Logger from "../../Common/Logger";
import { MemoryUsageInfo } from "../../Contracts/DataModels";
import { GitHubClient } from "../../GitHub/GitHubClient"; import { GitHubClient } from "../../GitHub/GitHubClient";
import { GitHubContentProvider } from "../../GitHub/GitHubContentProvider"; import { GitHubContentProvider } from "../../GitHub/GitHubContentProvider";
import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService"; import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService";
@@ -20,8 +18,10 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { getFullName } from "../../Utils/UserUtils"; import { getFullName } from "../../Utils/UserUtils";
import { useDialog } from "../Controls/Dialog";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane"; import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane";
import { GitHubReposPanel } from "../Panes/GitHubReposPanel/GitHubReposPanel";
import { PublishNotebookPane } from "../Panes/PublishNotebookPane/PublishNotebookPane"; import { PublishNotebookPane } from "../Panes/PublishNotebookPane/PublishNotebookPane";
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter"; import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
import { InMemoryContentProvider } from "./NotebookComponent/ContentProviders/InMemoryContentProvider"; import { InMemoryContentProvider } from "./NotebookComponent/ContentProviders/InMemoryContentProvider";
@@ -30,6 +30,7 @@ import { SnapshotRequest } from "./NotebookComponent/types";
import { NotebookContainerClient } from "./NotebookContainerClient"; import { NotebookContainerClient } from "./NotebookContainerClient";
import { NotebookContentClient } from "./NotebookContentClient"; import { NotebookContentClient } from "./NotebookContentClient";
import { SchemaAnalyzerNotebook } from "./SchemaAnalyzer/SchemaAnalyzerUtils"; import { SchemaAnalyzerNotebook } from "./SchemaAnalyzer/SchemaAnalyzerUtils";
import { useNotebook } from "./useNotebook";
type NotebookPaneContent = string | ImmutableNotebook; type NotebookPaneContent = string | ImmutableNotebook;
@@ -37,7 +38,6 @@ export type { NotebookPaneContent };
export interface NotebookManagerOptions { export interface NotebookManagerOptions {
container: Explorer; container: Explorer;
notebookBasePath: ko.Observable<string>;
resourceTree: ResourceTreeAdapter; resourceTree: ResourceTreeAdapter;
refreshCommandBarButtons: () => void; refreshCommandBarButtons: () => void;
refreshNotebookList: () => void; refreshNotebookList: () => void;
@@ -81,23 +81,28 @@ export default class NotebookManager {
contents.JupyterContentProvider contents.JupyterContentProvider
); );
this.notebookClient = new NotebookContainerClient( this.notebookClient = new NotebookContainerClient(() =>
this.params.container.notebookServerInfo, this.params.container.initNotebooks(userContext?.databaseAccount)
() => this.params.container.initNotebooks(userContext?.databaseAccount),
(update: MemoryUsageInfo) => this.params.container.memoryUsageInfo(update)
); );
this.notebookContentClient = new NotebookContentClient( this.notebookContentClient = new NotebookContentClient(this.notebookContentProvider);
this.params.container.notebookServerInfo,
this.params.notebookBasePath,
this.notebookContentProvider
);
this.gitHubOAuthService.getTokenObservable().subscribe((token) => { this.gitHubOAuthService.getTokenObservable().subscribe((token) => {
this.gitHubClient.setToken(token?.access_token); this.gitHubClient.setToken(token?.access_token);
if (this?.gitHubOAuthService.isLoggedIn()) { if (this?.gitHubOAuthService.isLoggedIn()) {
useSidePanel.getState().closeSidePanel(); useSidePanel.getState().closeSidePanel();
this.params.container.openGitHubReposPanel("Manager GitHub settings", this.junoClient); setTimeout(() => {
useSidePanel
.getState()
.openSidePanel(
"Manage GitHub settings",
<GitHubReposPanel
explorer={this.params.container}
gitHubClientProp={this.params.container.notebookManager.gitHubClient}
junoClientProp={this.junoClient}
/>
);
}, 200);
} }
this.params.refreshCommandBarButtons(); this.params.refreshCommandBarButtons();
@@ -107,6 +112,7 @@ export default class NotebookManager {
this.junoClient.subscribeToPinnedRepos((pinnedRepos) => { this.junoClient.subscribeToPinnedRepos((pinnedRepos) => {
this.params.resourceTree.initializeGitHubRepos(pinnedRepos); this.params.resourceTree.initializeGitHubRepos(pinnedRepos);
this.params.resourceTree.triggerRender(); this.params.resourceTree.triggerRender();
useNotebook.getState().initializeGitHubRepos(pinnedRepos);
}); });
this.refreshPinnedRepos(); this.refreshPinnedRepos();
} }
@@ -138,6 +144,7 @@ export default class NotebookManager {
notebookContentRef={notebookContentRef} notebookContentRef={notebookContentRef}
onTakeSnapshot={onTakeSnapshot} onTakeSnapshot={onTakeSnapshot}
/>, />,
"440px",
onClosePanel onClosePanel
); );
} }
@@ -166,11 +173,23 @@ export default class NotebookManager {
if (error.status === HttpStatusCodes.Unauthorized) { if (error.status === HttpStatusCodes.Unauthorized) {
this.gitHubOAuthService.resetToken(); this.gitHubOAuthService.resetToken();
this.params.container.showOkCancelModalDialog( useDialog
.getState()
.showOkCancelModalDialog(
undefined, undefined,
"Cosmos DB cannot access your Github account anymore. Please connect to GitHub again.", "Cosmos DB cannot access your Github account anymore. Please connect to GitHub again.",
"Connect to GitHub", "Connect to GitHub",
() => this.params.container.openGitHubReposPanel("Connect to GitHub"), () =>
useSidePanel
.getState()
.openSidePanel(
"Connect to GitHub",
<GitHubReposPanel
explorer={this.params.container}
gitHubClientProp={this.params.container.notebookManager.gitHubClient}
junoClientProp={this.junoClient}
/>
),
"Cancel", "Cancel",
undefined undefined
); );
@@ -180,7 +199,7 @@ export default class NotebookManager {
private promptForCommitMsg = (title: string, primaryButtonLabel: string) => { private promptForCommitMsg = (title: string, primaryButtonLabel: string) => {
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
let commitMsg = "Committed from Azure Cosmos DB Notebooks"; let commitMsg = "Committed from Azure Cosmos DB Notebooks";
this.params.container.showOkCancelModalDialog( useDialog.getState().showOkCancelModalDialog(
title || "Commit", title || "Commit",
undefined, undefined,
primaryButtonLabel || "Commit", primaryButtonLabel || "Commit",

View File

@@ -10,18 +10,30 @@
} }
.CodeMirror-scroll { .CodeMirror-scroll {
background-color: #f5f5f5; overflow: hidden !important;
} }
.CodeMirror-lines { .CodeMirror-lines {
cursor: default; cursor: default;
} }
.CodeMirror {
height: inherit;
}
.CodeMirror-scroll,
.CodeMirror-linenumber,
.CodeMirror-gutters {
background-color: #f5f5f5;
}
.nteract-cell:hover { .nteract-cell:hover {
border: 1px solid #0078d4; border: 1px solid #0078d4;
border-left: 3px solid #0078d4; border-left: 3px solid #0078d4;
.CodeMirror-scroll { .CodeMirror-scroll,
.CodeMirror-linenumber,
.CodeMirror-gutters {
background-color: #ffffff; background-color: #ffffff;
} }

View File

@@ -1,6 +1,6 @@
import { actions, ContentRef } from "@nteract/core"; import { actions, ContentRef } from "@nteract/core";
import { Cells, CodeCell, RawCell } from "@nteract/stateful-components"; import { Cells, CodeCell, RawCell } from "@nteract/stateful-components";
import MonacoEditor from "@nteract/stateful-components/lib/inputs/connected-editors/monacoEditor"; import CodeMirrorEditor from "@nteract/stateful-components/lib/inputs/connected-editors/codemirror";
import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor"; import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor";
import Prompt, { PassedPromptProps } from "@nteract/stateful-components/lib/inputs/prompt"; import Prompt, { PassedPromptProps } from "@nteract/stateful-components/lib/inputs/prompt";
import * as React from "react"; import * as React from "react";
@@ -67,8 +67,8 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
? () => <SandboxOutputs id={id} contentRef={contentRef} /> ? () => <SandboxOutputs id={id} contentRef={contentRef} />
: undefined, : undefined,
editor: { editor: {
monaco: (props: PassedEditorProps) => codemirror: (props: PassedEditorProps) =>
this.props.hideInputs ? <></> : <MonacoEditor readOnly={true} {...props} editorType={"monaco"} />, this.props.hideInputs ? <></> : <CodeMirrorEditor {...props} editorType="codemirror" />,
}, },
}} }}
</CodeCell> </CodeCell>
@@ -84,8 +84,8 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
<RawCell id={id} contentRef={contentRef} cell_type="raw"> <RawCell id={id} contentRef={contentRef} cell_type="raw">
{{ {{
editor: { editor: {
monaco: (props: PassedEditorProps) => codemirror: (props: PassedEditorProps) =>
this.props.hideInputs ? <></> : <MonacoEditor {...props} readOnly={true} editorType={"monaco"} />, this.props.hideInputs ? <></> : <CodeMirrorEditor {...props} editorType="codemirror" />,
}, },
}} }}
</RawCell> </RawCell>

View File

@@ -36,10 +36,20 @@
} }
} }
.CodeMirror-scroll, .CodeMirror-linenumber, .CodeMirror-gutters { .CodeMirror-scroll {
overflow: hidden !important;
}
.CodeMirror-scroll,
.CodeMirror-linenumber,
.CodeMirror-gutters {
background-color: #f5f5f5; background-color: #f5f5f5;
} }
.CodeMirror {
height: inherit;
}
.nteract-cell:hover { .nteract-cell:hover {
border: 1px solid @HoverColor; border: 1px solid @HoverColor;
border-left: 3px solid @HoverColor; border-left: 3px solid @HoverColor;
@@ -58,13 +68,16 @@
} }
// White background when hovered or selected // White background when hovered or selected
.nteract-cell:hover, .nteract-cell-container.selected .nteract-cell { .nteract-cell:hover,
.CodeMirror-scroll, .CodeMirror-linenumber, .CodeMirror-gutters { .nteract-cell-container.selected .nteract-cell {
.CodeMirror-scroll,
.CodeMirror-linenumber,
.CodeMirror-gutters {
background-color: #ffffff; background-color: #ffffff;
} }
.CodeMirror-linenumber { .CodeMirror-linenumber {
color: #015CDA; color: #015cda;
} }
.nteract-cell-outputs { .nteract-cell-outputs {
@@ -101,10 +114,9 @@
} }
} }
// Undo tree.less // Undo tree.less
.expanded::before { .expanded::before {
content: ''; content: "";
} }
.monaco-editor .monaco-list .main { .monaco-editor .monaco-list .main {

View File

@@ -2,11 +2,11 @@ import { CellId } from "@nteract/commutable";
import { CellType } from "@nteract/commutable/src"; import { CellType } from "@nteract/commutable/src";
import { actions, ContentRef, selectors } from "@nteract/core"; import { actions, ContentRef, selectors } from "@nteract/core";
import { Cells, CodeCell, RawCell } from "@nteract/stateful-components"; import { Cells, CodeCell, RawCell } from "@nteract/stateful-components";
import MonacoEditor from "@nteract/stateful-components/lib/inputs/connected-editors/monacoEditor"; import CodeMirrorEditor from "@nteract/stateful-components/lib/inputs/connected-editors/codemirror";
import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor"; import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor";
import * as React from "react"; import * as React from "react";
import { DndProvider } from "react-dnd"; import { DndProvider } from "react-dnd";
import HTML5Backend from "react-dnd-html5-backend"; import { HTML5Backend } from "react-dnd-html5-backend";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Dispatch } from "redux"; import { Dispatch } from "redux";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
@@ -14,6 +14,7 @@ import * as cdbActions from "../NotebookComponent/actions";
import loadTransform from "../NotebookComponent/loadTransform"; import loadTransform from "../NotebookComponent/loadTransform";
import { CdbAppState, SnapshotFragment, SnapshotRequest } from "../NotebookComponent/types"; import { CdbAppState, SnapshotFragment, SnapshotRequest } from "../NotebookComponent/types";
import { NotebookUtil } from "../NotebookUtil"; import { NotebookUtil } from "../NotebookUtil";
import SecurityWarningBar from "../SecurityWarningBar/SecurityWarningBar";
import { AzureTheme } from "./AzureTheme"; import { AzureTheme } from "./AzureTheme";
import "./base.css"; import "./base.css";
import CellCreator from "./decorators/CellCreator"; import CellCreator from "./decorators/CellCreator";
@@ -107,6 +108,7 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
return ( return (
<> <>
<div className="NotebookRendererContainer"> <div className="NotebookRendererContainer">
<SecurityWarningBar contentRef={this.props.contentRef} />
<div className="NotebookRenderer" ref={this.notebookRendererRef}> <div className="NotebookRenderer" ref={this.notebookRendererRef}>
<DndProvider backend={HTML5Backend}> <DndProvider backend={HTML5Backend}>
<KeyboardShortcuts contentRef={this.props.contentRef}> <KeyboardShortcuts contentRef={this.props.contentRef}>
@@ -120,7 +122,9 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
<CodeCell id={id} contentRef={contentRef} cell_type="code"> <CodeCell id={id} contentRef={contentRef} cell_type="code">
{{ {{
editor: { editor: {
monaco: (props: PassedEditorProps) => <MonacoEditor {...props} editorType={"monaco"} />, codemirror: (props: PassedEditorProps) => (
<CodeMirrorEditor {...props} editorType="codemirror" />
),
}, },
prompt: ({ id, contentRef }: { id: CellId; contentRef: ContentRef }) => ( prompt: ({ id, contentRef }: { id: CellId; contentRef: ContentRef }) => (
<Prompt id={id} contentRef={contentRef} isHovered={false}> <Prompt id={id} contentRef={contentRef} isHovered={false}>
@@ -142,7 +146,9 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
<MarkdownCell id={id} contentRef={contentRef} cell_type="markdown"> <MarkdownCell id={id} contentRef={contentRef} cell_type="markdown">
{{ {{
editor: { editor: {
monaco: (props: PassedEditorProps) => <MonacoEditor {...props} editorType={"monaco"} />, codemirror: (props: PassedEditorProps) => (
<CodeMirrorEditor {...props} editorType="codemirror" />
),
}, },
toolbar: () => <CellToolbar id={id} contentRef={contentRef} />, toolbar: () => <CellToolbar id={id} contentRef={contentRef} />,
}} }}
@@ -157,7 +163,9 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
<RawCell id={id} contentRef={contentRef} cell_type="raw"> <RawCell id={id} contentRef={contentRef} cell_type="raw">
{{ {{
editor: { editor: {
monaco: (props: PassedEditorProps) => <MonacoEditor {...props} editorType={"monaco"} />, codemirror: (props: PassedEditorProps) => (
<CodeMirrorEditor {...props} editorType="codemirror" />
),
}, },
toolbar: () => <CellToolbar id={id} contentRef={contentRef} />, toolbar: () => <CellToolbar id={id} contentRef={contentRef} />,
}} }}

Some files were not shown because too many files have changed in this diff Show More