mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-26 20:31:33 +00:00
Compare commits
42 Commits
users/srna
...
migrate/Qu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e20931fbfe | ||
|
|
1f12940d48 | ||
|
|
fecac5625a | ||
|
|
dc21032d69 | ||
|
|
39a67dbc98 | ||
|
|
ed9cf01b50 | ||
|
|
e443d17b2e | ||
|
|
401660ae15 | ||
|
|
c5e4ee9c2b | ||
|
|
913fec4e69 | ||
|
|
44315867ce | ||
|
|
6d46e48490 | ||
|
|
afacde4041 | ||
|
|
1f979a5fb7 | ||
|
|
8a3929775b | ||
|
|
b115bb34ca | ||
|
|
397231dca2 | ||
|
|
0bbf9de963 | ||
|
|
103b3bf6c9 | ||
|
|
7dd8bd567f | ||
|
|
416358f540 | ||
|
|
62b483d740 | ||
|
|
c665c4bb7a | ||
|
|
887618e77e | ||
|
|
8d6ccf8356 | ||
|
|
4614ab3427 | ||
|
|
71113e403e | ||
|
|
854bd2c149 | ||
|
|
cfce78242c | ||
|
|
ee3488d3a9 | ||
|
|
e8d320e505 | ||
|
|
474ec70923 | ||
|
|
f8ab0a82e0 | ||
|
|
f4eef1b61b | ||
|
|
1277940fe9 | ||
|
|
c486c1193e | ||
|
|
db34024259 | ||
|
|
98d7bb37d5 | ||
|
|
45d0b3f706 | ||
|
|
2b8327d222 | ||
|
|
a1d5648bbc | ||
|
|
1b74f619ab |
15
.env.example
15
.env.example
@@ -1,16 +1 @@
|
|||||||
PORTAL_RUNNER_USERNAME=
|
|
||||||
PORTAL_RUNNER_PASSWORD=
|
|
||||||
PORTAL_RUNNER_SUBSCRIPTION=
|
|
||||||
PORTAL_RUNNER_RESOURCE_GROUP=
|
|
||||||
PORTAL_RUNNER_DATABASE_ACCOUNT=
|
|
||||||
PORTAL_RUNNER_DATABASE_ACCOUNT_KEY=
|
|
||||||
PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT=
|
|
||||||
PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT_KEY=
|
|
||||||
PORTAL_RUNNER_CONNECTION_STRING=
|
|
||||||
NOTEBOOKS_TEST_RUNNER_TENANT_ID=
|
|
||||||
NOTEBOOKS_TEST_RUNNER_CLIENT_ID=
|
|
||||||
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET=
|
|
||||||
CASSANDRA_CONNECTION_STRING=
|
|
||||||
MONGO_CONNECTION_STRING=
|
|
||||||
TABLES_CONNECTION_STRING=
|
|
||||||
DATA_EXPLORER_ENDPOINT=https://localhost:1234/hostedExplorer.html
|
DATA_EXPLORER_ENDPOINT=https://localhost:1234/hostedExplorer.html
|
||||||
111
.eslintignore
111
.eslintignore
@@ -71,7 +71,6 @@ 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
|
||||||
@@ -83,11 +82,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,17 +99,10 @@ 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/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
|
||||||
@@ -141,116 +128,47 @@ 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/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/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/GraphVizComponent.tsx
|
src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx
|
||||||
@@ -258,46 +176,19 @@ 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
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
{
|
{
|
||||||
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com",
|
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com"
|
||||||
"enableSchemaAnalyzer": true
|
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
.dataResourceTree {
|
.dataResourceTree {
|
||||||
margin-left: @MediumSpace;
|
margin-left: @MediumSpace;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
.databaseHeader {
|
.databaseHeader {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|||||||
@@ -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,20 +44,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.contextmenushowing {
|
.contextmenushowing {
|
||||||
background-color: #EEE;
|
background-color: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collectionstree {
|
.collectionstree {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: @DefaultSpace;
|
margin-top: @DefaultSpace;
|
||||||
|
|
||||||
|
|
||||||
.databaseList {
|
.databaseList {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
|
|
||||||
.collectionList {
|
.collectionList {
|
||||||
padding-left:(2 * @MediumSpace);
|
padding-left: (2 * @MediumSpace);
|
||||||
}
|
}
|
||||||
|
|
||||||
.collectionChildList {
|
.collectionChildList {
|
||||||
@@ -85,7 +82,7 @@
|
|||||||
left: 0px;
|
left: 0px;
|
||||||
float: right;
|
float: right;
|
||||||
display: none;
|
display: none;
|
||||||
padding-left: 6px!important;
|
padding-left: 6px !important;
|
||||||
line-height: @TreeLineHeight;
|
line-height: @TreeLineHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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 {
|
||||||
@@ -257,11 +254,11 @@ ul.nav {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlight:hover>.contextmenubutton {
|
.highlight:hover > .contextmenubutton {
|
||||||
display: unset;
|
display: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlight:hover>.contextmenubutton::after {
|
.highlight:hover > .contextmenubutton::after {
|
||||||
content: "\2026";
|
content: "\2026";
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|||||||
29
package-lock.json
generated
29
package-lock.json
generated
@@ -5583,6 +5583,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
|
||||||
"integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA=="
|
"integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA=="
|
||||||
},
|
},
|
||||||
|
"@types/lodash": {
|
||||||
|
"version": "4.14.171",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.171.tgz",
|
||||||
|
"integrity": "sha512-7eQ2xYLLI/LsicL2nejW9Wyko3lcpN6O/z0ZLHrEQsg280zIdCv1t/0m6UtBjUHokCGBQ3gYTbHzDkZ1xOBwwg=="
|
||||||
|
},
|
||||||
"@types/minimatch": {
|
"@types/minimatch": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
|
||||||
@@ -20618,9 +20623,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"playwright": {
|
"playwright": {
|
||||||
"version": "1.10.0",
|
"version": "1.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.13.0.tgz",
|
||||||
"integrity": "sha512-b7SGBcCPq4W3pb4ImEDmNXtO0ZkJbZMuWiShsaNJd+rGfY/6fqwgllsAojmxGSgFmijYw7WxCoPiAIEDIH16Kw==",
|
"integrity": "sha512-GA5OyEeKx1v/pRcANmYncCT67Y7Y4N5zLRU5E690dn/Id10sooR5hQZmCDYsjXlutZb/1q0R3sITALnvhEjCjg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"commander": "^6.1.0",
|
"commander": "^6.1.0",
|
||||||
@@ -20635,7 +20640,8 @@
|
|||||||
"proxy-from-env": "^1.1.0",
|
"proxy-from-env": "^1.1.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"stack-utils": "^2.0.3",
|
"stack-utils": "^2.0.3",
|
||||||
"ws": "^7.3.1"
|
"ws": "^7.4.6",
|
||||||
|
"yazl": "^2.5.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": {
|
"commander": {
|
||||||
@@ -20667,6 +20673,12 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"escape-string-regexp": "^2.0.0"
|
"escape-string-regexp": "^2.0.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"ws": {
|
||||||
|
"version": "7.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz",
|
||||||
|
"integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==",
|
||||||
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -26157,6 +26169,15 @@
|
|||||||
"fd-slicer": "~1.1.0"
|
"fd-slicer": "~1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"yazl": {
|
||||||
|
"version": "2.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz",
|
||||||
|
"integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"buffer-crc32": "~0.2.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"yocto-queue": {
|
"yocto-queue": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
"@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",
|
||||||
@@ -163,7 +164,7 @@
|
|||||||
"mini-css-extract-plugin": "0.4.3",
|
"mini-css-extract-plugin": "0.4.3",
|
||||||
"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",
|
||||||
"raw-loader": "0.5.1",
|
"raw-loader": "0.5.1",
|
||||||
"react-dev-utils": "11.0.4",
|
"react-dev-utils": "11.0.4",
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ 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";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AfecFeatures {
|
export class AfecFeatures {
|
||||||
|
|||||||
@@ -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()", () => {
|
||||||
|
|||||||
@@ -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 */}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
jest.mock("../../Utils/arm/request");
|
jest.mock("../../Utils/arm/request");
|
||||||
jest.mock("../CosmosClient");
|
jest.mock("../CosmosClient");
|
||||||
|
import ko from "knockout";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels";
|
import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels";
|
||||||
|
import { Database } from "../../Contracts/ViewModels";
|
||||||
|
import { useDatabases } from "../../Explorer/useDatabases";
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
import { armRequest } from "../../Utils/arm/request";
|
import { armRequest } from "../../Utils/arm/request";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
@@ -23,6 +26,15 @@ describe("createCollection", () => {
|
|||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
apiType: "SQL",
|
apiType: "SQL",
|
||||||
});
|
});
|
||||||
|
useDatabases.setState({
|
||||||
|
databases: [
|
||||||
|
{
|
||||||
|
id: ko.observable("testDatabase"),
|
||||||
|
loadCollections: () => undefined,
|
||||||
|
collections: ko.observableArray([]),
|
||||||
|
} as Database,
|
||||||
|
],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call ARM if logged in with AAD", async () => {
|
it("should call ARM if logged in with AAD", async () => {
|
||||||
|
|||||||
@@ -4,20 +4,16 @@ import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/Contai
|
|||||||
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
|
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
|
import { useDatabases } from "../../Explorer/useDatabases";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import {
|
import { getCollectionName } from "../../Utils/APITypeUtils";
|
||||||
createUpdateCassandraTable,
|
import { createUpdateCassandraTable } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
||||||
getCassandraTable,
|
import { createUpdateGremlinGraph } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
||||||
} from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
import { createUpdateMongoDBCollection } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
|
||||||
import { createUpdateGremlinGraph, getGremlinGraph } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
import { createUpdateSqlContainer } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
|
||||||
import {
|
import { createUpdateTable } from "../../Utils/arm/generatedClients/cosmos/tableResources";
|
||||||
createUpdateMongoDBCollection,
|
|
||||||
getMongoDBCollection,
|
|
||||||
} from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
|
|
||||||
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
|
|
||||||
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/cosmos/tableResources";
|
|
||||||
import * as ARMTypes from "../../Utils/arm/generatedClients/cosmos/types";
|
import * as ARMTypes from "../../Utils/arm/generatedClients/cosmos/types";
|
||||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
@@ -59,6 +55,16 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||||
|
if (!params.createNewDatabase) {
|
||||||
|
const isValid = await useDatabases.getState().validateCollectionId(params.databaseId, params.collectionId);
|
||||||
|
if (!isValid) {
|
||||||
|
const collectionName = getCollectionName().toLocaleLowerCase();
|
||||||
|
throw new Error(
|
||||||
|
`Create ${collectionName} failed: ${collectionName} with id ${params.collectionId} already exists`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const { apiType } = userContext;
|
const { apiType } = userContext;
|
||||||
switch (apiType) {
|
switch (apiType) {
|
||||||
case "SQL":
|
case "SQL":
|
||||||
@@ -77,23 +83,6 @@ const createCollectionWithARM = async (params: DataModels.CreateCollectionParams
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createSqlContainer = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
const createSqlContainer = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||||
try {
|
|
||||||
const getResponse = await getSqlContainer(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId,
|
|
||||||
params.collectionId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create container failed: container with id ${params.collectionId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
||||||
const resource: ARMTypes.SqlContainerResource = {
|
const resource: ARMTypes.SqlContainerResource = {
|
||||||
id: params.collectionId,
|
id: params.collectionId,
|
||||||
@@ -131,23 +120,6 @@ const createSqlContainer = async (params: DataModels.CreateCollectionParams): Pr
|
|||||||
|
|
||||||
const createMongoCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
const createMongoCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||||
const mongoWildcardIndexOnAllFields: ARMTypes.MongoIndex[] = [{ key: { keys: ["$**"] } }, { key: { keys: ["_id"] } }];
|
const mongoWildcardIndexOnAllFields: ARMTypes.MongoIndex[] = [{ key: { keys: ["$**"] } }, { key: { keys: ["_id"] } }];
|
||||||
try {
|
|
||||||
const getResponse = await getMongoDBCollection(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId,
|
|
||||||
params.collectionId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create collection failed: collection with id ${params.collectionId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
||||||
const resource: ARMTypes.MongoDBCollectionResource = {
|
const resource: ARMTypes.MongoDBCollectionResource = {
|
||||||
id: params.collectionId,
|
id: params.collectionId,
|
||||||
@@ -189,23 +161,6 @@ const createMongoCollection = async (params: DataModels.CreateCollectionParams):
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createCassandraTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
const createCassandraTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||||
try {
|
|
||||||
const getResponse = await getCassandraTable(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId,
|
|
||||||
params.collectionId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create table failed: table with id ${params.collectionId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
||||||
const resource: ARMTypes.CassandraTableResource = {
|
const resource: ARMTypes.CassandraTableResource = {
|
||||||
id: params.collectionId,
|
id: params.collectionId,
|
||||||
@@ -233,23 +188,6 @@ const createCassandraTable = async (params: DataModels.CreateCollectionParams):
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createGraph = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
const createGraph = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||||
try {
|
|
||||||
const getResponse = await getGremlinGraph(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId,
|
|
||||||
params.collectionId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create graph failed: graph with id ${params.collectionId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
||||||
const resource: ARMTypes.GremlinGraphResource = {
|
const resource: ARMTypes.GremlinGraphResource = {
|
||||||
id: params.collectionId,
|
id: params.collectionId,
|
||||||
@@ -284,22 +222,6 @@ const createGraph = async (params: DataModels.CreateCollectionParams): Promise<D
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
const createTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||||
try {
|
|
||||||
const getResponse = await getTable(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.collectionId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create table failed: table with id ${params.collectionId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
||||||
const resource: ARMTypes.TableResource = {
|
const resource: ARMTypes.TableResource = {
|
||||||
id: params.collectionId,
|
id: params.collectionId,
|
||||||
|
|||||||
@@ -2,20 +2,13 @@ import { DatabaseResponse } from "@azure/cosmos";
|
|||||||
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
|
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
|
import { useDatabases } from "../../Explorer/useDatabases";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import {
|
import { getDatabaseName } from "../../Utils/APITypeUtils";
|
||||||
createUpdateCassandraKeyspace,
|
import { createUpdateCassandraKeyspace } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
||||||
getCassandraKeyspace,
|
import { createUpdateGremlinDatabase } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
||||||
} from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
import { createUpdateMongoDBDatabase } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
|
||||||
import {
|
import { createUpdateSqlDatabase } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
|
||||||
createUpdateGremlinDatabase,
|
|
||||||
getGremlinDatabase,
|
|
||||||
} from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
|
||||||
import {
|
|
||||||
createUpdateMongoDBDatabase,
|
|
||||||
getMongoDBDatabase,
|
|
||||||
} from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
|
|
||||||
import { createUpdateSqlDatabase, getSqlDatabase } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
|
|
||||||
import {
|
import {
|
||||||
CassandraKeyspaceCreateUpdateParameters,
|
CassandraKeyspaceCreateUpdateParameters,
|
||||||
CreateUpdateOptions,
|
CreateUpdateOptions,
|
||||||
@@ -48,6 +41,11 @@ export async function createDatabase(params: DataModels.CreateDatabaseParams): P
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||||
|
if (!useDatabases.getState().validateDatabaseId(params.databaseId)) {
|
||||||
|
const databaseName = getDatabaseName().toLocaleLowerCase();
|
||||||
|
throw new Error(`Create ${databaseName} failed: ${databaseName} with id ${params.databaseId} already exists`);
|
||||||
|
}
|
||||||
|
|
||||||
const { apiType } = userContext;
|
const { apiType } = userContext;
|
||||||
|
|
||||||
switch (apiType) {
|
switch (apiType) {
|
||||||
@@ -65,22 +63,6 @@ async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): P
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createSqlDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
async function createSqlDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||||
try {
|
|
||||||
const getResponse = await getSqlDatabase(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: CreateUpdateOptions = constructRpOptions(params);
|
const options: CreateUpdateOptions = constructRpOptions(params);
|
||||||
const rpPayload: SqlDatabaseCreateUpdateParameters = {
|
const rpPayload: SqlDatabaseCreateUpdateParameters = {
|
||||||
properties: {
|
properties: {
|
||||||
@@ -101,22 +83,6 @@ async function createSqlDatabase(params: DataModels.CreateDatabaseParams): Promi
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createMongoDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
async function createMongoDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||||
try {
|
|
||||||
const getResponse = await getMongoDBDatabase(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: CreateUpdateOptions = constructRpOptions(params);
|
const options: CreateUpdateOptions = constructRpOptions(params);
|
||||||
const rpPayload: MongoDBDatabaseCreateUpdateParameters = {
|
const rpPayload: MongoDBDatabaseCreateUpdateParameters = {
|
||||||
properties: {
|
properties: {
|
||||||
@@ -137,22 +103,6 @@ async function createMongoDatabase(params: DataModels.CreateDatabaseParams): Pro
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createCassandraKeyspace(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
async function createCassandraKeyspace(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||||
try {
|
|
||||||
const getResponse = await getCassandraKeyspace(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: CreateUpdateOptions = constructRpOptions(params);
|
const options: CreateUpdateOptions = constructRpOptions(params);
|
||||||
const rpPayload: CassandraKeyspaceCreateUpdateParameters = {
|
const rpPayload: CassandraKeyspaceCreateUpdateParameters = {
|
||||||
properties: {
|
properties: {
|
||||||
@@ -173,22 +123,6 @@ async function createCassandraKeyspace(params: DataModels.CreateDatabaseParams):
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createGremlineDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
async function createGremlineDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||||
try {
|
|
||||||
const getResponse = await getGremlinDatabase(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: CreateUpdateOptions = constructRpOptions(params);
|
const options: CreateUpdateOptions = constructRpOptions(params);
|
||||||
const rpPayload: GremlinDatabaseCreateUpdateParameters = {
|
const rpPayload: GremlinDatabaseCreateUpdateParameters = {
|
||||||
properties: {
|
properties: {
|
||||||
|
|||||||
@@ -27,7 +27,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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { userContext } from "../UserContext";
|
|||||||
import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils";
|
import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils";
|
||||||
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
|
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
|
||||||
import Explorer from "./Explorer";
|
import Explorer from "./Explorer";
|
||||||
|
import { useNotebook } from "./Notebook/useNotebook";
|
||||||
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
|
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
|
||||||
import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel";
|
import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel";
|
||||||
import StoredProcedure from "./Tree/StoredProcedure";
|
import StoredProcedure from "./Tree/StoredProcedure";
|
||||||
@@ -49,7 +50,10 @@ export const createDatabaseContextMenu = (container: Explorer, databaseId: strin
|
|||||||
onClick: () =>
|
onClick: () =>
|
||||||
useSidePanel
|
useSidePanel
|
||||||
.getState()
|
.getState()
|
||||||
.openSidePanel("Delete " + getDatabaseName(), <DeleteDatabaseConfirmationPanel explorer={container} />),
|
.openSidePanel(
|
||||||
|
"Delete " + getDatabaseName(),
|
||||||
|
<DeleteDatabaseConfirmationPanel refreshDatabases={() => container.refreshAllDatabases()} />
|
||||||
|
),
|
||||||
label: `Delete ${getDatabaseName()}`,
|
label: `Delete ${getDatabaseName()}`,
|
||||||
styleClass: "deleteDatabaseMenuItem",
|
styleClass: "deleteDatabaseMenuItem",
|
||||||
});
|
});
|
||||||
@@ -81,13 +85,13 @@ export const createCollectionContextMenuButton = (
|
|||||||
iconSrc: HostedTerminalIcon,
|
iconSrc: HostedTerminalIcon,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
if (container.isShellEnabled()) {
|
if (useNotebook.getState().isShellEnabled) {
|
||||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||||
} else {
|
} else {
|
||||||
selectedCollection && selectedCollection.onNewMongoShellClick();
|
selectedCollection && selectedCollection.onNewMongoShellClick();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
label: container.isShellEnabled() ? "Open Mongo Shell" : "New Shell",
|
label: useNotebook.getState().isShellEnabled ? "Open Mongo Shell" : "New Shell",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +109,7 @@ export const createCollectionContextMenuButton = (
|
|||||||
iconSrc: AddUdfIcon,
|
iconSrc: AddUdfIcon,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, undefined);
|
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection);
|
||||||
},
|
},
|
||||||
label: "New UDF",
|
label: "New UDF",
|
||||||
});
|
});
|
||||||
@@ -125,7 +129,10 @@ export const createCollectionContextMenuButton = (
|
|||||||
onClick: () =>
|
onClick: () =>
|
||||||
useSidePanel
|
useSidePanel
|
||||||
.getState()
|
.getState()
|
||||||
.openSidePanel("Delete " + getCollectionName(), <DeleteCollectionConfirmationPane explorer={container} />),
|
.openSidePanel(
|
||||||
|
"Delete " + getCollectionName(),
|
||||||
|
<DeleteCollectionConfirmationPane refreshDatabases={() => container.refreshAllDatabases()} />
|
||||||
|
),
|
||||||
label: `Delete ${getCollectionName()}`,
|
label: `Delete ${getCollectionName()}`,
|
||||||
styleClass: "deleteCollectionMenuItem",
|
styleClass: "deleteCollectionMenuItem",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
|||||||
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());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export class GitHubReposComponent extends React.Component<GitHubReposComponentPr
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={"paneMainContent"}>{content}</div>
|
<div>{content}</div>
|
||||||
{!this.props.showAuthorizeAccess && (
|
{!this.props.showAuthorizeAccess && (
|
||||||
<>
|
<>
|
||||||
<div className={"paneFooter"} style={ContentFooterStyle}>
|
<div className={"paneFooter"} style={ContentFooterStyle}>
|
||||||
|
|||||||
@@ -1,154 +1,90 @@
|
|||||||
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import { NotebookTerminalComponent } from "./NotebookTerminalComponent";
|
import { NotebookTerminalComponent, NotebookTerminalComponentProps } from "./NotebookTerminalComponent";
|
||||||
|
|
||||||
const createTestDatabaseAccount = (): DataModels.DatabaseAccount => {
|
const testAccount: DataModels.DatabaseAccount = {
|
||||||
return {
|
id: "id",
|
||||||
id: "testId",
|
kind: "kind",
|
||||||
kind: "testKind",
|
location: "location",
|
||||||
location: "testLocation",
|
name: "name",
|
||||||
name: "testName",
|
|
||||||
properties: {
|
properties: {
|
||||||
cassandraEndpoint: null,
|
|
||||||
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
|
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
|
||||||
gremlinEndpoint: null,
|
|
||||||
tableEndpoint: null,
|
|
||||||
},
|
},
|
||||||
type: "testType",
|
type: "type",
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createTestMongo32DatabaseAccount = (): DataModels.DatabaseAccount => {
|
const testMongo32Account: DataModels.DatabaseAccount = {
|
||||||
return {
|
...testAccount,
|
||||||
id: "testId",
|
|
||||||
kind: "testKind",
|
|
||||||
location: "testLocation",
|
|
||||||
name: "testName",
|
|
||||||
properties: {
|
|
||||||
cassandraEndpoint: null,
|
|
||||||
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
|
|
||||||
gremlinEndpoint: null,
|
|
||||||
tableEndpoint: null,
|
|
||||||
},
|
|
||||||
type: "testType",
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createTestMongo36DatabaseAccount = (): DataModels.DatabaseAccount => {
|
const testMongo36Account: DataModels.DatabaseAccount = {
|
||||||
return {
|
...testAccount,
|
||||||
id: "testId",
|
|
||||||
kind: "testKind",
|
|
||||||
location: "testLocation",
|
|
||||||
name: "testName",
|
|
||||||
properties: {
|
properties: {
|
||||||
cassandraEndpoint: null,
|
|
||||||
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
|
|
||||||
gremlinEndpoint: null,
|
|
||||||
tableEndpoint: null,
|
|
||||||
mongoEndpoint: "https://testMongoEndpoint.azure.com/",
|
mongoEndpoint: "https://testMongoEndpoint.azure.com/",
|
||||||
},
|
},
|
||||||
type: "testType",
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createTestCassandraDatabaseAccount = (): DataModels.DatabaseAccount => {
|
const testCassandraAccount: DataModels.DatabaseAccount = {
|
||||||
return {
|
...testAccount,
|
||||||
id: "testId",
|
|
||||||
kind: "testKind",
|
|
||||||
location: "testLocation",
|
|
||||||
name: "testName",
|
|
||||||
properties: {
|
properties: {
|
||||||
cassandraEndpoint: "https://testCassandraEndpoint.azure.com/",
|
cassandraEndpoint: "https://testCassandraEndpoint.azure.com/",
|
||||||
documentEndpoint: null,
|
|
||||||
gremlinEndpoint: null,
|
|
||||||
tableEndpoint: null,
|
|
||||||
},
|
},
|
||||||
type: "testType",
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createTerminal = (): NotebookTerminalComponent => {
|
const testNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||||
return new NotebookTerminalComponent({
|
authToken: "authToken",
|
||||||
notebookServerInfo: {
|
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com",
|
||||||
authToken: "testAuthToken",
|
|
||||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/",
|
|
||||||
},
|
|
||||||
databaseAccount: createTestDatabaseAccount(),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createMongo32Terminal = (): NotebookTerminalComponent => {
|
const testMongoNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||||
return new NotebookTerminalComponent({
|
authToken: "authToken",
|
||||||
notebookServerInfo: {
|
|
||||||
authToken: "testAuthToken",
|
|
||||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
|
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
|
||||||
},
|
|
||||||
databaseAccount: createTestMongo32DatabaseAccount(),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createMongo36Terminal = (): NotebookTerminalComponent => {
|
const testCassandraNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||||
return new NotebookTerminalComponent({
|
authToken: "authToken",
|
||||||
notebookServerInfo: {
|
|
||||||
authToken: "testAuthToken",
|
|
||||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
|
|
||||||
},
|
|
||||||
databaseAccount: createTestMongo36DatabaseAccount(),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const createCassandraTerminal = (): NotebookTerminalComponent => {
|
|
||||||
return new NotebookTerminalComponent({
|
|
||||||
notebookServerInfo: {
|
|
||||||
authToken: "testAuthToken",
|
|
||||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/cassandra",
|
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/cassandra",
|
||||||
},
|
|
||||||
databaseAccount: createTestCassandraDatabaseAccount(),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("NotebookTerminalComponent", () => {
|
describe("NotebookTerminalComponent", () => {
|
||||||
it("getTerminalParams: Test for terminal", () => {
|
it("renders terminal", () => {
|
||||||
const terminal: NotebookTerminalComponent = createTerminal();
|
const props: NotebookTerminalComponentProps = {
|
||||||
const params: Map<string, string> = terminal.getTerminalParams();
|
databaseAccount: testAccount,
|
||||||
|
notebookServerInfo: testNotebookServerInfo,
|
||||||
|
};
|
||||||
|
|
||||||
expect(params).toEqual(
|
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||||
new Map<string, string>([["terminal", "true"]])
|
expect(wrapper).toMatchSnapshot();
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("getTerminalParams: Test for Mongo 3.2 terminal", () => {
|
it("renders mongo 3.2 shell", () => {
|
||||||
const terminal: NotebookTerminalComponent = createMongo32Terminal();
|
const props: NotebookTerminalComponentProps = {
|
||||||
const params: Map<string, string> = terminal.getTerminalParams();
|
databaseAccount: testMongo32Account,
|
||||||
|
notebookServerInfo: testMongoNotebookServerInfo,
|
||||||
|
};
|
||||||
|
|
||||||
expect(params).toEqual(
|
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||||
new Map<string, string>([
|
expect(wrapper).toMatchSnapshot();
|
||||||
["terminal", "true"],
|
|
||||||
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.documentEndpoint).host],
|
|
||||||
])
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("getTerminalParams: Test for Mongo 3.6 terminal", () => {
|
it("renders mongo 3.6 shell", () => {
|
||||||
const terminal: NotebookTerminalComponent = createMongo36Terminal();
|
const props: NotebookTerminalComponentProps = {
|
||||||
const params: Map<string, string> = terminal.getTerminalParams();
|
databaseAccount: testMongo36Account,
|
||||||
|
notebookServerInfo: testMongoNotebookServerInfo,
|
||||||
|
};
|
||||||
|
|
||||||
expect(params).toEqual(
|
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||||
new Map<string, string>([
|
expect(wrapper).toMatchSnapshot();
|
||||||
["terminal", "true"],
|
|
||||||
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.mongoEndpoint).host],
|
|
||||||
])
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("getTerminalParams: Test for Cassandra terminal", () => {
|
it("renders cassandra shell", () => {
|
||||||
const terminal: NotebookTerminalComponent = createCassandraTerminal();
|
const props: NotebookTerminalComponentProps = {
|
||||||
const params: Map<string, string> = terminal.getTerminalParams();
|
databaseAccount: testCassandraAccount,
|
||||||
|
notebookServerInfo: testCassandraNotebookServerInfo,
|
||||||
|
};
|
||||||
|
|
||||||
expect(params).toEqual(
|
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||||
new Map<string, string>([
|
expect(wrapper).toMatchSnapshot();
|
||||||
["terminal", "true"],
|
|
||||||
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.cassandraEndpoint).host],
|
|
||||||
])
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
* Wrapper around Notebook server terminal
|
* Wrapper around Notebook server terminal
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import postRobot from "post-robot";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import * as StringUtils from "../../../Utils/StringUtils";
|
import { TerminalProps } from "../../../Terminal/TerminalProps";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { TerminalQueryParams } from "../../../Common/Constants";
|
import * as StringUtils from "../../../Utils/StringUtils";
|
||||||
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
|
||||||
|
|
||||||
export interface NotebookTerminalComponentProps {
|
export interface NotebookTerminalComponentProps {
|
||||||
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
||||||
@@ -15,79 +15,69 @@ export interface NotebookTerminalComponentProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class NotebookTerminalComponent extends React.Component<NotebookTerminalComponentProps> {
|
export class NotebookTerminalComponent extends React.Component<NotebookTerminalComponentProps> {
|
||||||
|
private terminalWindow: Window;
|
||||||
|
|
||||||
constructor(props: NotebookTerminalComponentProps) {
|
constructor(props: NotebookTerminalComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
this.sendPropsToTerminalFrame();
|
||||||
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className="notebookTerminalContainer">
|
<div className="notebookTerminalContainer">
|
||||||
<iframe
|
<iframe
|
||||||
title="Terminal to Notebook Server"
|
title="Terminal to Notebook Server"
|
||||||
src={NotebookTerminalComponent.createNotebookAppSrc(this.props.notebookServerInfo, this.getTerminalParams())}
|
onLoad={(event) => this.handleFrameLoad(event)}
|
||||||
|
src="terminal.html"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTerminalParams(): Map<string, string> {
|
handleFrameLoad(event: React.SyntheticEvent<HTMLIFrameElement, Event>): void {
|
||||||
let params: Map<string, string> = new Map<string, string>();
|
this.terminalWindow = (event.target as HTMLIFrameElement).contentWindow;
|
||||||
params.set(TerminalQueryParams.Terminal, "true");
|
this.sendPropsToTerminalFrame();
|
||||||
|
|
||||||
const terminalEndpoint: string = this.tryGetTerminalEndpoint();
|
|
||||||
if (terminalEndpoint) {
|
|
||||||
params.set(TerminalQueryParams.TerminalEndpoint, terminalEndpoint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return params;
|
sendPropsToTerminalFrame(): void {
|
||||||
|
if (!this.terminalWindow) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public tryGetTerminalEndpoint(): string | null {
|
const props: TerminalProps = {
|
||||||
let terminalEndpoint: string | null;
|
terminalEndpoint: this.tryGetTerminalEndpoint(),
|
||||||
|
notebookServerEndpoint: this.props.notebookServerInfo?.notebookServerEndpoint,
|
||||||
|
authToken: this.props.notebookServerInfo?.authToken,
|
||||||
|
subscriptionId: userContext.subscriptionId,
|
||||||
|
apiType: userContext.apiType,
|
||||||
|
authType: userContext.authType,
|
||||||
|
databaseAccount: userContext.databaseAccount,
|
||||||
|
};
|
||||||
|
|
||||||
const notebookServerEndpoint: string = this.props.notebookServerInfo.notebookServerEndpoint;
|
postRobot.send(this.terminalWindow, "props", props, {
|
||||||
|
domain: window.location.origin,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public tryGetTerminalEndpoint(): string | undefined {
|
||||||
|
let terminalEndpoint: string | undefined;
|
||||||
|
|
||||||
|
const notebookServerEndpoint = this.props.notebookServerInfo?.notebookServerEndpoint;
|
||||||
if (StringUtils.endsWith(notebookServerEndpoint, "mongo")) {
|
if (StringUtils.endsWith(notebookServerEndpoint, "mongo")) {
|
||||||
let mongoShellEndpoint: string = this.props.databaseAccount.properties.mongoEndpoint;
|
// mongoEndpoint is only available for Mongo 3.6 and higher, fallback to documentEndpoint otherwise
|
||||||
if (!mongoShellEndpoint) {
|
terminalEndpoint =
|
||||||
// mongoEndpoint is only available for Mongo 3.6 and higher.
|
this.props.databaseAccount?.properties.mongoEndpoint || this.props.databaseAccount?.properties.documentEndpoint;
|
||||||
// Fallback to documentEndpoint otherwise.
|
|
||||||
mongoShellEndpoint = this.props.databaseAccount.properties.documentEndpoint;
|
|
||||||
}
|
|
||||||
terminalEndpoint = mongoShellEndpoint;
|
|
||||||
} else if (StringUtils.endsWith(notebookServerEndpoint, "cassandra")) {
|
} else if (StringUtils.endsWith(notebookServerEndpoint, "cassandra")) {
|
||||||
terminalEndpoint = this.props.databaseAccount.properties.cassandraEndpoint;
|
terminalEndpoint = this.props.databaseAccount?.properties.cassandraEndpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (terminalEndpoint) {
|
if (terminalEndpoint) {
|
||||||
return new URL(terminalEndpoint).host;
|
return new URL(terminalEndpoint).host;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static createNotebookAppSrc(
|
return undefined;
|
||||||
serverInfo: DataModels.NotebookWorkspaceConnectionInfo,
|
|
||||||
params: Map<string, string>
|
|
||||||
): string {
|
|
||||||
if (!serverInfo.notebookServerEndpoint) {
|
|
||||||
handleError(
|
|
||||||
"Notebook server endpoint not defined. Terminal will fail to connect to jupyter server.",
|
|
||||||
"NotebookTerminalComponent/createNotebookAppSrc"
|
|
||||||
);
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
params.set(TerminalQueryParams.Server, serverInfo.notebookServerEndpoint);
|
|
||||||
if (serverInfo.authToken && serverInfo.authToken.length > 0) {
|
|
||||||
params.set(TerminalQueryParams.Token, serverInfo.authToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
params.set(TerminalQueryParams.SubscriptionId, userContext.subscriptionId);
|
|
||||||
|
|
||||||
let result: string = "terminal.html?";
|
|
||||||
for (let key of params.keys()) {
|
|
||||||
result += `${key}=${encodeURIComponent(params.get(key))}&`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`NotebookTerminalComponent renders cassandra shell 1`] = `
|
||||||
|
<div
|
||||||
|
className="notebookTerminalContainer"
|
||||||
|
>
|
||||||
|
<iframe
|
||||||
|
onLoad={[Function]}
|
||||||
|
src="terminal.html"
|
||||||
|
title="Terminal to Notebook Server"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`NotebookTerminalComponent renders mongo 3.2 shell 1`] = `
|
||||||
|
<div
|
||||||
|
className="notebookTerminalContainer"
|
||||||
|
>
|
||||||
|
<iframe
|
||||||
|
onLoad={[Function]}
|
||||||
|
src="terminal.html"
|
||||||
|
title="Terminal to Notebook Server"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`NotebookTerminalComponent renders mongo 3.6 shell 1`] = `
|
||||||
|
<div
|
||||||
|
className="notebookTerminalContainer"
|
||||||
|
>
|
||||||
|
<iframe
|
||||||
|
onLoad={[Function]}
|
||||||
|
src="terminal.html"
|
||||||
|
title="Terminal to Notebook Server"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`NotebookTerminalComponent renders terminal 1`] = `
|
||||||
|
<div
|
||||||
|
className="notebookTerminalContainer"
|
||||||
|
>
|
||||||
|
<iframe
|
||||||
|
onLoad={[Function]}
|
||||||
|
src="terminal.html"
|
||||||
|
title="Terminal to Notebook Server"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@@ -30,7 +30,7 @@ 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";
|
||||||
|
|
||||||
const title: string = "Open Saved Queries";
|
const title = "Open Saved Queries";
|
||||||
|
|
||||||
export interface QueriesGridComponentProps {
|
export interface QueriesGridComponentProps {
|
||||||
queriesClient: QueriesClient;
|
queriesClient: QueriesClient;
|
||||||
@@ -196,9 +196,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 +214,15 @@ 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>,
|
|
||||||
menuItem: any
|
|
||||||
) => {
|
|
||||||
if (window.confirm("Are you sure you want to delete this query?")) {
|
if (window.confirm("Are you sure you want to delete this query?")) {
|
||||||
const container = window.dataExplorer;
|
|
||||||
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,
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import { trace, traceFailure, traceStart, traceSuccess } from "../../../Shared/T
|
|||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
|
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
|
||||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
|
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
|
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
|
||||||
@@ -110,7 +109,6 @@ export interface SettingsComponentState {
|
|||||||
|
|
||||||
initialNotification: DataModels.Notification;
|
initialNotification: DataModels.Notification;
|
||||||
selectedTab: SettingsV2TabTypes;
|
selectedTab: SettingsV2TabTypes;
|
||||||
offerLoaded: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> {
|
export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> {
|
||||||
@@ -195,7 +193,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
|
|
||||||
initialNotification: undefined,
|
initialNotification: undefined,
|
||||||
selectedTab: SettingsV2TabTypes.ScaleTab,
|
selectedTab: SettingsV2TabTypes.ScaleTab,
|
||||||
offerLoaded: !!this.offer,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.saveSettingsButton = {
|
this.saveSettingsButton = {
|
||||||
@@ -217,7 +214,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
if (this.isCollectionSettingsTab) {
|
if (this.isCollectionSettingsTab) {
|
||||||
this.refreshIndexTransformationProgress();
|
this.refreshIndexTransformationProgress();
|
||||||
this.loadMongoIndexes();
|
this.loadMongoIndexes();
|
||||||
this.loadCollectionOffer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setAutoPilotStates();
|
this.setAutoPilotStates();
|
||||||
@@ -372,34 +368,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private async loadCollectionOffer() {
|
|
||||||
try {
|
|
||||||
this.props.settingsTab.isExecuting(true);
|
|
||||||
await this.collection.loadOffer();
|
|
||||||
this.props.settingsTab.tabTitle(this.collection.offer() ? "Settings" : "Scale & Settings");
|
|
||||||
this.setState({ offerLoaded: true });
|
|
||||||
} catch (error) {
|
|
||||||
this.props.settingsTab.isExecutionError(true);
|
|
||||||
const errorMessage = getErrorMessage(error);
|
|
||||||
traceFailure(
|
|
||||||
Action.Tab,
|
|
||||||
{
|
|
||||||
databaseName: this.collection.databaseId,
|
|
||||||
collectionName: this.collection.id(),
|
|
||||||
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.props.settingsTab.tabTitle,
|
|
||||||
error: errorMessage,
|
|
||||||
errorStack: getErrorStack(error),
|
|
||||||
},
|
|
||||||
this.props.settingsTab.onLoadStartKey
|
|
||||||
);
|
|
||||||
logConsoleError(`Error while fetching container settings for container ${this.collection.id()}: ${errorMessage}`);
|
|
||||||
} finally {
|
|
||||||
this.props.settingsTab.isExecuting(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getMongoIndexesToSave = (): MongoIndex[] => {
|
private getMongoIndexesToSave = (): MongoIndex[] => {
|
||||||
let finalIndexes: MongoIndex[] = [];
|
let finalIndexes: MongoIndex[] = [];
|
||||||
this.state.currentMongoIndexes?.map((mongoIndex: MongoIndex, index: number) => {
|
this.state.currentMongoIndexes?.map((mongoIndex: MongoIndex, index: number) => {
|
||||||
@@ -937,10 +905,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.state.offerLoaded) {
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const subSettingsComponentProps: SubSettingsComponentProps = {
|
const subSettingsComponentProps: SubSettingsComponentProps = {
|
||||||
collection: this.collection,
|
collection: this.collection,
|
||||||
isAnalyticalStorageEnabled: this.isAnalyticalStorageEnabled,
|
isAnalyticalStorageEnabled: this.isAnalyticalStorageEnabled,
|
||||||
|
|||||||
@@ -30,17 +30,8 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"container": Explorer {
|
"container": Explorer {
|
||||||
"_isInitializingNotebooks": false,
|
"_isInitializingNotebooks": false,
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"isAccountReady": [Function],
|
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
"isNotebookEnabled": [Function],
|
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
|
||||||
"isSchemaEnabled": [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],
|
||||||
@@ -48,21 +39,11 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
},
|
},
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"resourceTokenCollection": [Function],
|
|
||||||
"resourceTree": ResourceTreeAdapter {
|
"resourceTree": ResourceTreeAdapter {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"copyNotebook": [Function],
|
"copyNotebook": [Function],
|
||||||
"parameters": [Function],
|
"parameters": [Function],
|
||||||
},
|
},
|
||||||
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
|
||||||
"container": [Circular],
|
|
||||||
"parameters": [Function],
|
|
||||||
},
|
|
||||||
"sparkClusterConnectionInfo": [Function],
|
|
||||||
"tabsManager": TabsManager {
|
|
||||||
"activeTab": [Function],
|
|
||||||
"openedTabs": [Function],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"databaseId": "test",
|
"databaseId": "test",
|
||||||
"defaultTtl": [Function],
|
"defaultTtl": [Function],
|
||||||
@@ -116,17 +97,8 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"container": Explorer {
|
"container": Explorer {
|
||||||
"_isInitializingNotebooks": false,
|
"_isInitializingNotebooks": false,
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"isAccountReady": [Function],
|
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
"isNotebookEnabled": [Function],
|
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
|
||||||
"isSchemaEnabled": [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],
|
||||||
@@ -134,21 +106,11 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
},
|
},
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"resourceTokenCollection": [Function],
|
|
||||||
"resourceTree": ResourceTreeAdapter {
|
"resourceTree": ResourceTreeAdapter {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"copyNotebook": [Function],
|
"copyNotebook": [Function],
|
||||||
"parameters": [Function],
|
"parameters": [Function],
|
||||||
},
|
},
|
||||||
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
|
||||||
"container": [Circular],
|
|
||||||
"parameters": [Function],
|
|
||||||
},
|
|
||||||
"sparkClusterConnectionInfo": [Function],
|
|
||||||
"tabsManager": TabsManager {
|
|
||||||
"activeTab": [Function],
|
|
||||||
"openedTabs": [Function],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"databaseId": "test",
|
"databaseId": "test",
|
||||||
"defaultTtl": [Function],
|
"defaultTtl": [Function],
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ 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 { 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 { useSelectedNode } from "../../useSelectedNode";
|
||||||
@@ -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 (
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ 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 { useSelectedNode } from "../../useSelectedNode";
|
||||||
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
||||||
|
|
||||||
@@ -27,9 +29,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
},
|
},
|
||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
});
|
});
|
||||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
|
||||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Account is not serverless - button should be visible", () => {
|
it("Account is not serverless - button should be visible", () => {
|
||||||
@@ -70,18 +69,19 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
},
|
},
|
||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
});
|
});
|
||||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
portalEnv: "prod",
|
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);
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
||||||
@@ -89,8 +89,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Account is running on one of the national clouds - button should be hidden", () => {
|
it("Account is running on one of the national clouds - button should be hidden", () => {
|
||||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
portalEnv: "mooncake",
|
portalEnv: "mooncake",
|
||||||
});
|
});
|
||||||
@@ -101,8 +99,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Notebooks is not enabled but is available - button should be shown and enabled", () => {
|
it("Notebooks is not enabled but is available - button should be shown and enabled", () => {
|
||||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
||||||
@@ -112,9 +109,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => {
|
it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => {
|
||||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
||||||
expect(enableNotebookBtn).toBeDefined();
|
expect(enableNotebookBtn).toBeDefined();
|
||||||
@@ -138,24 +132,25 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
},
|
},
|
||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
});
|
});
|
||||||
mockExplorer.isSynapseLinkUpdating = ko.observable(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.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", () => {
|
||||||
@@ -184,7 +179,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Notebooks is not enabled and is available - button should be hidden", () => {
|
it("Notebooks is not enabled and is available - button should be hidden", () => {
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||||
@@ -192,7 +187,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
||||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
useNotebook.getState().setIsNotebookEnabled(true);
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||||
@@ -202,8 +197,8 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
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, selectedNodeState);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||||
@@ -213,9 +208,9 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
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, selectedNodeState);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||||
@@ -236,7 +231,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
},
|
},
|
||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
});
|
});
|
||||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -247,8 +241,11 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
},
|
},
|
||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
});
|
});
|
||||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
});
|
||||||
mockExplorer.isNotebooksEnabledForAccount = 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", () => {
|
||||||
@@ -259,7 +256,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
},
|
},
|
||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
});
|
});
|
||||||
console.log(mockExplorer);
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
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();
|
||||||
@@ -282,7 +278,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Notebooks is not enabled and is available - button should be shown and enabled", () => {
|
it("Notebooks is not enabled and is available - button should be shown and enabled", () => {
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||||
@@ -290,7 +286,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
||||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
useNotebook.getState().setIsNotebookEnabled(true);
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||||
@@ -300,8 +296,8 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
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, selectedNodeState);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||||
@@ -326,23 +322,17 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
});
|
});
|
||||||
|
|
||||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
|
||||||
|
|
||||||
mockExplorer.notebookManager = new NotebookManager();
|
mockExplorer.notebookManager = new NotebookManager();
|
||||||
mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined);
|
mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
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, selectedNodeState);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel);
|
const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel);
|
||||||
@@ -350,7 +340,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
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, selectedNodeState);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
@@ -376,10 +366,11 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
describe("Resource token", () => {
|
describe("Resource token", () => {
|
||||||
const mockCollection = { id: ko.observable("test") } as CollectionBase;
|
const mockCollection = { id: ko.observable("test") } as CollectionBase;
|
||||||
useSelectedNode.getState().setSelectedNode(mockCollection);
|
useSelectedNode.getState().setSelectedNode(mockCollection);
|
||||||
|
useDatabases.setState({ resourceTokenCollection: mockCollection });
|
||||||
const selectedNodeState = useSelectedNode.getState();
|
const selectedNodeState = useSelectedNode.getState();
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
mockExplorer = {} as Explorer;
|
mockExplorer = {} as Explorer;
|
||||||
mockExplorer.resourceTokenCollection = ko.observable(mockCollection);
|
|
||||||
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
authType: AuthType.ResourceToken,
|
authType: AuthType.ResourceToken,
|
||||||
|
|||||||
@@ -22,15 +22,22 @@ 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 { isServerlessAccount } from "../../../Utils/CapabilityUtils";
|
||||||
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
|
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
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 { SettingsPane } from "../../Panes/SettingsPane/SettingsPane";
|
||||||
|
import { SetupNoteBooksPanel } from "../../Panes/SetupNotebooksPanel/SetupNotebooksPanel";
|
||||||
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { SelectedNodeState } from "../../useSelectedNode";
|
import { SelectedNodeState } from "../../useSelectedNode";
|
||||||
|
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
@@ -62,7 +69,7 @@ export function createStaticCommandBarButtons(
|
|||||||
|
|
||||||
buttons.push(createDivider());
|
buttons.push(createDivider());
|
||||||
|
|
||||||
if (container.isNotebookEnabled()) {
|
if (useNotebook.getState().isNotebookEnabled) {
|
||||||
const newNotebookButton = createNewNotebookButton(container);
|
const newNotebookButton = createNewNotebookButton(container);
|
||||||
newNotebookButton.children = [createNewNotebookButton(container), createuploadNotebookButton(container)];
|
newNotebookButton.children = [createNewNotebookButton(container), createuploadNotebookButton(container)];
|
||||||
buttons.push(newNotebookButton);
|
buttons.push(newNotebookButton);
|
||||||
@@ -76,7 +83,7 @@ export function createStaticCommandBarButtons(
|
|||||||
buttons.push(createNotebookWorkspaceResetButton(container));
|
buttons.push(createNotebookWorkspaceResetButton(container));
|
||||||
if (
|
if (
|
||||||
(userContext.apiType === "Mongo" &&
|
(userContext.apiType === "Mongo" &&
|
||||||
container.isShellEnabled() &&
|
useNotebook.getState().isShellEnabled &&
|
||||||
selectedNodeState.isDatabaseNodeOrNoneSelected()) ||
|
selectedNodeState.isDatabaseNodeOrNoneSelected()) ||
|
||||||
userContext.apiType === "Cassandra"
|
userContext.apiType === "Cassandra"
|
||||||
) {
|
) {
|
||||||
@@ -138,13 +145,13 @@ export function createContextCommandBarButtons(
|
|||||||
const buttons: CommandButtonComponentProps[] = [];
|
const buttons: CommandButtonComponentProps[] = [];
|
||||||
|
|
||||||
if (!selectedNodeState.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 = selectedNodeState.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||||
if (container.isShellEnabled()) {
|
if (useNotebook.getState().isShellEnabled) {
|
||||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||||
} else {
|
} else {
|
||||||
selectedCollection && selectedCollection.onNewMongoShellClick();
|
selectedCollection && selectedCollection.onNewMongoShellClick();
|
||||||
@@ -269,7 +276,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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -279,9 +286,8 @@ 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,
|
||||||
@@ -413,7 +419,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,
|
||||||
@@ -446,12 +453,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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,15 +488,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,
|
||||||
@@ -501,15 +520,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,
|
||||||
@@ -536,10 +561,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,
|
||||||
@@ -554,12 +590,12 @@ function createStaticCommandBarButtonsForResourceToken(
|
|||||||
const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState);
|
const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState);
|
||||||
const openQueryBtn = createOpenQueryButton(container);
|
const openQueryBtn = createOpenQueryButton(container);
|
||||||
|
|
||||||
|
const resourceTokenCollection: ViewModels.CollectionBase = useDatabases.getState().resourceTokenCollection;
|
||||||
const isResourceTokenCollectionNodeSelected: boolean =
|
const isResourceTokenCollectionNodeSelected: boolean =
|
||||||
container.resourceTokenCollection() &&
|
resourceTokenCollection?.id() === selectedNodeState.selectedNode?.id();
|
||||||
container.resourceTokenCollection().id() === selectedNodeState.selectedNode?.id();
|
|
||||||
newSqlQueryBtn.disabled = !isResourceTokenCollectionNodeSelected;
|
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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -185,12 +183,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 />,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
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 * as NteractUtil from "../NTeractUtil";
|
import * as NteractUtil from "../NTeractUtil";
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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 undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ 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";
|
||||||
@@ -776,7 +777,9 @@ const closeUnsupportedMimetypesEpic = (
|
|||||||
if (explorer && !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.`;
|
||||||
@@ -804,7 +807,9 @@ const closeContentFailedToFetchEpic = (
|
|||||||
if (explorer) {
|
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}.`;
|
||||||
|
|||||||
@@ -8,25 +8,26 @@ import * as DataModels from "../../Contracts/DataModels";
|
|||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { createOrUpdate, destroy } from "../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,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);
|
||||||
@@ -98,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);
|
||||||
@@ -117,15 +120,11 @@ 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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
@@ -59,8 +66,11 @@ export class NotebookContentClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteContentItem(item: NotebookContentItem): Promise<void> {
|
public async deleteContentItem(item: NotebookContentItem): Promise<void> {
|
||||||
return this.deleteNotebookFile(item.path).then((path: string) => {
|
const path = await this.deleteNotebookFile(item.path);
|
||||||
|
useNotebook.getState().deleteNotebookItem(item);
|
||||||
|
|
||||||
|
// 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 +80,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;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -271,9 +280,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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
@@ -22,6 +20,7 @@ import { userContext } from "../../UserContext";
|
|||||||
import { getFullName } from "../../Utils/UserUtils";
|
import { getFullName } from "../../Utils/UserUtils";
|
||||||
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";
|
||||||
@@ -37,7 +36,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 +79,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();
|
||||||
@@ -138,6 +141,7 @@ export default class NotebookManager {
|
|||||||
notebookContentRef={notebookContentRef}
|
notebookContentRef={notebookContentRef}
|
||||||
onTakeSnapshot={onTakeSnapshot}
|
onTakeSnapshot={onTakeSnapshot}
|
||||||
/>,
|
/>,
|
||||||
|
"440px",
|
||||||
onClosePanel
|
onClosePanel
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -170,7 +174,17 @@ export default class NotebookManager {
|
|||||||
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
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import "./Prompt.less";
|
|||||||
|
|
||||||
export const promptContent = (props: PassedPromptProps): JSX.Element => {
|
export const promptContent = (props: PassedPromptProps): JSX.Element => {
|
||||||
if (props.status === "busy") {
|
if (props.status === "busy") {
|
||||||
const stopButtonText: string = "Stop cell execution";
|
const stopButtonText = "Stop cell execution";
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{ position: "sticky", width: "100%", maxHeight: "100%", left: 0, top: 0, zIndex: 300 }}
|
style={{ position: "sticky", width: "100%", maxHeight: "100%", left: 0, top: 0, zIndex: 300 }}
|
||||||
@@ -23,7 +23,7 @@ export const promptContent = (props: PassedPromptProps): JSX.Element => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (props.isHovered) {
|
} else if (props.isHovered) {
|
||||||
const playButtonText: string = "Run cell";
|
const playButtonText = "Run cell";
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
className="runCellButton"
|
className="runCellButton"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { StatusBar } from "./StatusBar";
|
import { StatusBar } from "./StatusBar";
|
||||||
|
|
||||||
describe("StatusBar", () => {
|
describe("StatusBar", () => {
|
||||||
@@ -28,8 +27,8 @@ describe("StatusBar", () => {
|
|||||||
kernelSpecDisplayName: "javascript",
|
kernelSpecDisplayName: "javascript",
|
||||||
kernelStatus: "kernelStatus",
|
kernelStatus: "kernelStatus",
|
||||||
},
|
},
|
||||||
null,
|
undefined,
|
||||||
null
|
undefined
|
||||||
);
|
);
|
||||||
expect(shouldUpdate).toBe(true);
|
expect(shouldUpdate).toBe(true);
|
||||||
});
|
});
|
||||||
@@ -47,8 +46,8 @@ describe("StatusBar", () => {
|
|||||||
kernelSpecDisplayName: "python3",
|
kernelSpecDisplayName: "python3",
|
||||||
kernelStatus: "kernelStatus",
|
kernelStatus: "kernelStatus",
|
||||||
},
|
},
|
||||||
null,
|
undefined,
|
||||||
null
|
undefined
|
||||||
);
|
);
|
||||||
expect(shouldUpdate).toBe(true);
|
expect(shouldUpdate).toBe(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { AppState, ContentRef, selectors } from "@nteract/core";
|
|||||||
import distanceInWordsToNow from "date-fns/distance_in_words_to_now";
|
import distanceInWordsToNow from "date-fns/distance_in_words_to_now";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
import styled from "styled-components";
|
||||||
import { StyleConstants } from "../../../Common/Constants";
|
import { StyleConstants } from "../../../Common/Constants";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -12,8 +13,6 @@ interface Props {
|
|||||||
|
|
||||||
const NOT_CONNECTED = "not connected";
|
const NOT_CONNECTED = "not connected";
|
||||||
|
|
||||||
import styled from "styled-components";
|
|
||||||
|
|
||||||
export const LeftStatus = styled.div`
|
export const LeftStatus = styled.div`
|
||||||
float: left;
|
float: left;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -80,7 +79,7 @@ interface InitialProps {
|
|||||||
contentRef: ContentRef;
|
contentRef: ContentRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
const makeMapStateToProps = (initialState: AppState, initialProps: InitialProps): ((state: AppState) => Props) => {
|
const makeMapStateToProps = (_initialState: AppState, initialProps: InitialProps): ((state: AppState) => Props) => {
|
||||||
const { contentRef } = initialProps;
|
const { contentRef } = initialProps;
|
||||||
|
|
||||||
const mapStateToProps = (state: AppState) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
@@ -90,26 +89,26 @@ const makeMapStateToProps = (initialState: AppState, initialProps: InitialProps)
|
|||||||
return {
|
return {
|
||||||
kernelStatus: NOT_CONNECTED,
|
kernelStatus: NOT_CONNECTED,
|
||||||
kernelSpecDisplayName: "no kernel",
|
kernelSpecDisplayName: "no kernel",
|
||||||
lastSaved: null,
|
lastSaved: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const kernelRef = content.model.kernelRef;
|
const kernelRef = content.model.kernelRef;
|
||||||
let kernel = null;
|
let kernel;
|
||||||
if (kernelRef) {
|
if (kernelRef) {
|
||||||
kernel = selectors.kernel(state, { kernelRef });
|
kernel = selectors.kernel(state, { kernelRef });
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastSaved = content && content.lastSaved ? content.lastSaved : null;
|
const lastSaved = content && content.lastSaved ? content.lastSaved : undefined;
|
||||||
|
|
||||||
const kernelStatus = kernel != null && kernel.status != null ? kernel.status : NOT_CONNECTED;
|
const kernelStatus = kernel?.status || NOT_CONNECTED;
|
||||||
|
|
||||||
// TODO: We need kernels associated to the kernelspec they came from
|
// TODO: We need kernels associated to the kernelspec they came from
|
||||||
// so we can pluck off the display_name and provide it here
|
// so we can pluck off the display_name and provide it here
|
||||||
let kernelSpecDisplayName = " ";
|
let kernelSpecDisplayName = " ";
|
||||||
if (kernelStatus === NOT_CONNECTED) {
|
if (kernelStatus === NOT_CONNECTED) {
|
||||||
kernelSpecDisplayName = "no kernel";
|
kernelSpecDisplayName = "no kernel";
|
||||||
} else if (kernel != null && kernel.kernelSpecName != null) {
|
} else if (kernel?.kernelSpecName) {
|
||||||
kernelSpecDisplayName = kernel.kernelSpecName;
|
kernelSpecDisplayName = kernel.kernelSpecName;
|
||||||
} else if (content && content.type === "notebook") {
|
} else if (content && content.type === "notebook") {
|
||||||
kernelSpecDisplayName = selectors.notebook.displayName(content.model) || " ";
|
kernelSpecDisplayName = selectors.notebook.displayName(content.model) || " ";
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ interface DispatchProps {
|
|||||||
moveCell: (destinationId: CellId, above: boolean) => void;
|
moveCell: (destinationId: CellId, above: boolean) => void;
|
||||||
clearOutputs: () => void;
|
clearOutputs: () => void;
|
||||||
deleteCell: () => void;
|
deleteCell: () => void;
|
||||||
traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: any) => void;
|
traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: string) => void;
|
||||||
takeNotebookSnapshot: (payload: SnapshotRequest) => void;
|
takeNotebookSnapshot: (payload: SnapshotRequest) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ const mapDispatchToProps = (
|
|||||||
dispatch(actions.moveCell({ id, contentRef, destinationId, above })),
|
dispatch(actions.moveCell({ id, contentRef, destinationId, above })),
|
||||||
clearOutputs: () => dispatch(actions.clearOutputs({ id, contentRef })),
|
clearOutputs: () => dispatch(actions.clearOutputs({ id, contentRef })),
|
||||||
deleteCell: () => dispatch(actions.deleteCell({ id, contentRef })),
|
deleteCell: () => dispatch(actions.deleteCell({ id, contentRef })),
|
||||||
traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: any) =>
|
traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: string) =>
|
||||||
dispatch(cdbActions.traceNotebookTelemetry({ action, actionModifier, data })),
|
dispatch(cdbActions.traceNotebookTelemetry({ action, actionModifier, data })),
|
||||||
takeNotebookSnapshot: (request: SnapshotRequest) => dispatch(cdbActions.takeNotebookSnapshot(request)),
|
takeNotebookSnapshot: (request: SnapshotRequest) => dispatch(cdbActions.takeNotebookSnapshot(request)),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
|
import { ContentRef } from "@nteract/core";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Dispatch } from "redux";
|
import { Dispatch } from "redux";
|
||||||
|
|
||||||
import { ContentRef } from "@nteract/core";
|
|
||||||
import * as actions from "../../NotebookComponent/actions";
|
import * as actions from "../../NotebookComponent/actions";
|
||||||
|
|
||||||
interface ComponentProps {
|
interface ComponentProps {
|
||||||
@@ -29,10 +28,7 @@ class HoverableCell extends React.Component<ComponentProps & DispatchProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
const mapDispatchToProps = (dispatch: Dispatch, { id }: { id: string }): DispatchProps => ({
|
||||||
dispatch: Dispatch,
|
|
||||||
{ id, contentRef }: { id: string; contentRef: ContentRef }
|
|
||||||
): DispatchProps => ({
|
|
||||||
hover: () => dispatch(actions.setHoveredCell({ cellId: id })),
|
hover: () => dispatch(actions.setHoveredCell({ cellId: id })),
|
||||||
unHover: () => dispatch(actions.setHoveredCell({ cellId: undefined })),
|
unHover: () => dispatch(actions.setHoveredCell({ cellId: undefined })),
|
||||||
});
|
});
|
||||||
|
|||||||
205
src/Explorer/Notebook/useNotebook.ts
Normal file
205
src/Explorer/Notebook/useNotebook.ts
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
import create, { UseStore } from "zustand";
|
||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import * as Constants from "../../Common/Constants";
|
||||||
|
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||||
|
import * as Logger from "../../Common/Logger";
|
||||||
|
import { configContext } from "../../ConfigContext";
|
||||||
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
||||||
|
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
||||||
|
import NotebookManager from "./NotebookManager";
|
||||||
|
|
||||||
|
interface NotebookState {
|
||||||
|
isNotebookEnabled: boolean;
|
||||||
|
isNotebooksEnabledForAccount: boolean;
|
||||||
|
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
||||||
|
sparkClusterConnectionInfo: DataModels.SparkClusterConnectionInfo;
|
||||||
|
isSynapseLinkUpdating: boolean;
|
||||||
|
memoryUsageInfo: DataModels.MemoryUsageInfo;
|
||||||
|
isShellEnabled: boolean;
|
||||||
|
notebookBasePath: string;
|
||||||
|
isInitializingNotebooks: boolean;
|
||||||
|
myNotebooksContentRoot: NotebookContentItem;
|
||||||
|
gitHubNotebooksContentRoot: NotebookContentItem;
|
||||||
|
galleryContentRoot: NotebookContentItem;
|
||||||
|
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
|
||||||
|
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
|
||||||
|
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
|
||||||
|
setSparkClusterConnectionInfo: (sparkClusterConnectionInfo: DataModels.SparkClusterConnectionInfo) => void;
|
||||||
|
setIsSynapseLinkUpdating: (isSynapseLinkUpdating: boolean) => void;
|
||||||
|
setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => void;
|
||||||
|
setIsShellEnabled: (isShellEnabled: boolean) => void;
|
||||||
|
setNotebookBasePath: (notebookBasePath: string) => void;
|
||||||
|
refreshNotebooksEnabledStateForAccount: () => Promise<void>;
|
||||||
|
findItem: (root: NotebookContentItem, item: NotebookContentItem) => NotebookContentItem;
|
||||||
|
updateNotebookItem: (item: NotebookContentItem) => void;
|
||||||
|
deleteNotebookItem: (item: NotebookContentItem) => void;
|
||||||
|
initializeNotebooksTree: (notebookManager: NotebookManager) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||||
|
isNotebookEnabled: false,
|
||||||
|
isNotebooksEnabledForAccount: false,
|
||||||
|
notebookServerInfo: {
|
||||||
|
notebookServerEndpoint: undefined,
|
||||||
|
authToken: undefined,
|
||||||
|
},
|
||||||
|
sparkClusterConnectionInfo: {
|
||||||
|
userName: undefined,
|
||||||
|
password: undefined,
|
||||||
|
endpoints: [],
|
||||||
|
},
|
||||||
|
isSynapseLinkUpdating: false,
|
||||||
|
memoryUsageInfo: undefined,
|
||||||
|
isShellEnabled: false,
|
||||||
|
notebookBasePath: Constants.Notebook.defaultBasePath,
|
||||||
|
isInitializingNotebooks: false,
|
||||||
|
myNotebooksContentRoot: undefined,
|
||||||
|
gitHubNotebooksContentRoot: undefined,
|
||||||
|
galleryContentRoot: undefined,
|
||||||
|
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
||||||
|
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
||||||
|
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
||||||
|
set({ notebookServerInfo }),
|
||||||
|
setSparkClusterConnectionInfo: (sparkClusterConnectionInfo: DataModels.SparkClusterConnectionInfo) =>
|
||||||
|
set({ sparkClusterConnectionInfo }),
|
||||||
|
setIsSynapseLinkUpdating: (isSynapseLinkUpdating: boolean) => set({ isSynapseLinkUpdating }),
|
||||||
|
setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => set({ memoryUsageInfo }),
|
||||||
|
setIsShellEnabled: (isShellEnabled: boolean) => set({ isShellEnabled }),
|
||||||
|
setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }),
|
||||||
|
refreshNotebooksEnabledStateForAccount: async (): Promise<void> => {
|
||||||
|
const { databaseAccount, authType } = userContext;
|
||||||
|
if (
|
||||||
|
authType === AuthType.EncryptedToken ||
|
||||||
|
authType === AuthType.ResourceToken ||
|
||||||
|
authType === AuthType.MasterKey
|
||||||
|
) {
|
||||||
|
set({ isNotebooksEnabledForAccount: false });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstWriteLocation =
|
||||||
|
databaseAccount?.properties?.writeLocations &&
|
||||||
|
databaseAccount?.properties?.writeLocations[0]?.locationName.toLowerCase();
|
||||||
|
const disallowedLocationsUri = `${configContext.BACKEND_ENDPOINT}/api/disallowedLocations`;
|
||||||
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
|
try {
|
||||||
|
const response = await fetch(disallowedLocationsUri, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
resourceTypes: [Constants.ArmResourceTypes.notebookWorkspaces],
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
[authorizationHeader.header]: authorizationHeader.token,
|
||||||
|
[Constants.HttpHeaders.contentType]: "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to fetch disallowed locations");
|
||||||
|
}
|
||||||
|
|
||||||
|
const disallowedLocations: string[] = await response.json();
|
||||||
|
if (!disallowedLocations) {
|
||||||
|
Logger.logInfo("No disallowed locations found", "Explorer/isNotebooksEnabledForAccount");
|
||||||
|
set({ isNotebooksEnabledForAccount: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// firstWriteLocation should not be disallowed
|
||||||
|
const isAccountInAllowedLocation = firstWriteLocation && disallowedLocations.indexOf(firstWriteLocation) === -1;
|
||||||
|
set({ isNotebooksEnabledForAccount: isAccountInAllowedLocation });
|
||||||
|
} catch (error) {
|
||||||
|
Logger.logError(getErrorMessage(error), "Explorer/isNotebooksEnabledForAccount");
|
||||||
|
set({ isNotebooksEnabledForAccount: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
findItem: (root: NotebookContentItem, item: NotebookContentItem): NotebookContentItem => {
|
||||||
|
const currentItem = root || get().myNotebooksContentRoot;
|
||||||
|
|
||||||
|
if (currentItem) {
|
||||||
|
if (currentItem.path === item.path && currentItem.name === item.name) {
|
||||||
|
return currentItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentItem.children) {
|
||||||
|
for (const childItem of currentItem.children) {
|
||||||
|
const result = get().findItem(childItem, item);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
updateNotebookItem: (item: NotebookContentItem): void => {
|
||||||
|
const root = cloneDeep(get().myNotebooksContentRoot);
|
||||||
|
const parentItem = get().findItem(root, item.parent);
|
||||||
|
parentItem.children = parentItem.children.filter((child) => child.path !== item.path);
|
||||||
|
parentItem.children.push(item);
|
||||||
|
item.parent = parentItem;
|
||||||
|
set({ myNotebooksContentRoot: root });
|
||||||
|
},
|
||||||
|
deleteNotebookItem: (item: NotebookContentItem): void => {
|
||||||
|
const root = cloneDeep(get().myNotebooksContentRoot);
|
||||||
|
const parentItem = get().findItem(root, item.parent);
|
||||||
|
parentItem.children = parentItem.children.filter((child) => child.path !== item.path);
|
||||||
|
set({ myNotebooksContentRoot: root });
|
||||||
|
},
|
||||||
|
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
||||||
|
const myNotebooksContentRoot = {
|
||||||
|
name: "My Notebooks",
|
||||||
|
path: get().notebookBasePath,
|
||||||
|
type: NotebookContentItemType.Directory,
|
||||||
|
};
|
||||||
|
const galleryContentRoot = {
|
||||||
|
name: "Gallery",
|
||||||
|
path: "Gallery",
|
||||||
|
type: NotebookContentItemType.File,
|
||||||
|
};
|
||||||
|
const gitHubNotebooksContentRoot = notebookManager?.gitHubOAuthService?.isLoggedIn()
|
||||||
|
? {
|
||||||
|
name: "GitHub repos",
|
||||||
|
path: "PsuedoDir",
|
||||||
|
type: NotebookContentItemType.Directory,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
set({
|
||||||
|
myNotebooksContentRoot,
|
||||||
|
galleryContentRoot,
|
||||||
|
gitHubNotebooksContentRoot,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (get().notebookServerInfo?.notebookServerEndpoint) {
|
||||||
|
const updatedRoot = await notebookManager?.notebookContentClient?.updateItemChildren(myNotebooksContentRoot);
|
||||||
|
set({ myNotebooksContentRoot: updatedRoot });
|
||||||
|
|
||||||
|
if (updatedRoot?.children) {
|
||||||
|
// Count 1st generation children (tree is lazy-loaded)
|
||||||
|
const nodeCounts = { files: 0, notebooks: 0, directories: 0 };
|
||||||
|
updatedRoot.children.forEach((notebookItem) => {
|
||||||
|
switch (notebookItem.type) {
|
||||||
|
case NotebookContentItemType.File:
|
||||||
|
nodeCounts.files++;
|
||||||
|
break;
|
||||||
|
case NotebookContentItemType.Directory:
|
||||||
|
nodeCounts.directories++;
|
||||||
|
break;
|
||||||
|
case NotebookContentItemType.Notebook:
|
||||||
|
nodeCounts.notebooks++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
TelemetryProcessor.trace(Action.RefreshResourceTreeMyNotebooks, ActionModifiers.Mark, { ...nodeCounts });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
||||||
@@ -113,7 +113,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
collectionId: "",
|
collectionId: "",
|
||||||
enableIndexing: true,
|
enableIndexing: true,
|
||||||
isSharded: userContext.apiType !== "Tables",
|
isSharded: userContext.apiType !== "Tables",
|
||||||
partitionKey: "",
|
partitionKey:
|
||||||
|
(userContext.features.partitionKeyDefault && userContext.apiType === "SQL") ||
|
||||||
|
(userContext.features.partitionKeyDefault && userContext.apiType === "Mongo")
|
||||||
|
? "/id"
|
||||||
|
: "",
|
||||||
enableDedicatedThroughput: false,
|
enableDedicatedThroughput: false,
|
||||||
createMongoWildCardIndex: isCapabilityEnabled("EnableMongo"),
|
createMongoWildCardIndex: isCapabilityEnabled("EnableMongo"),
|
||||||
useHashV2: false,
|
useHashV2: false,
|
||||||
@@ -413,6 +417,10 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
<Text variant="small" aria-label="pkDescription">
|
||||||
|
{this.getPartitionKeySubtext()}
|
||||||
|
</Text>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="addCollection-partitionKeyValue"
|
id="addCollection-partitionKeyValue"
|
||||||
@@ -807,6 +815,17 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
return tooltipText;
|
return tooltipText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getPartitionKeySubtext(): string {
|
||||||
|
if (
|
||||||
|
userContext.features.partitionKeyDefault &&
|
||||||
|
(userContext.apiType === "SQL" || userContext.apiType === "Mongo")
|
||||||
|
) {
|
||||||
|
const subtext = "For small workloads, the item ID is a suitable choice for the partition key.";
|
||||||
|
return subtext;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
private getAnalyticalStorageTooltipContent(): JSX.Element {
|
private getAnalyticalStorageTooltipContent(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Text variant="small">
|
<Text variant="small">
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { logError } from "../../../Common/Logger";
|
|||||||
import { Query } from "../../../Contracts/DataModels";
|
import { Query } from "../../../Contracts/DataModels";
|
||||||
import { Collection } from "../../../Contracts/ViewModels";
|
import { Collection } from "../../../Contracts/ViewModels";
|
||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { trace } from "../../../Shared/Telemetry/TelemetryProcessor";
|
import { trace } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
@@ -36,7 +37,7 @@ export const BrowseQueriesPane: FunctionComponent<BrowseQueriesPaneProps> = ({
|
|||||||
selectedCollection.onNewQueryClick(selectedCollection, undefined, savedQuery.query);
|
selectedCollection.onNewQueryClick(selectedCollection, undefined, savedQuery.query);
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryTab = explorer && (explorer.tabsManager.activeTab() as NewQueryTab);
|
const queryTab = useTabs.getState().activeTab as NewQueryTab;
|
||||||
queryTab.tabTitle(savedQuery.queryName);
|
queryTab.tabTitle(savedQuery.queryName);
|
||||||
queryTab.tabPath(`${selectedCollection.databaseId}>${selectedCollection.id()}>${savedQuery.queryName}`);
|
queryTab.tabPath(`${selectedCollection.databaseId}>${selectedCollection.id()}>${savedQuery.queryName}`);
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
|||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { NotebookContentItem, NotebookContentItemType } from "../../Notebook/NotebookContentItem";
|
import { NotebookContentItem, NotebookContentItemType } from "../../Notebook/NotebookContentItem";
|
||||||
|
import { useNotebook } from "../../Notebook/useNotebook";
|
||||||
import { ResourceTreeAdapter } from "../../Tree/ResourceTreeAdapter";
|
import { ResourceTreeAdapter } from "../../Tree/ResourceTreeAdapter";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent";
|
import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent";
|
||||||
@@ -101,7 +102,7 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
|||||||
case "MyNotebooks":
|
case "MyNotebooks":
|
||||||
parent = {
|
parent = {
|
||||||
name: ResourceTreeAdapter.MyNotebooksTitle,
|
name: ResourceTreeAdapter.MyNotebooksTitle,
|
||||||
path: container.getNotebookBasePath(),
|
path: useNotebook.getState().notebookBasePath,
|
||||||
type: NotebookContentItemType.Directory,
|
type: NotebookContentItemType.Directory,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { Collection, Database } from "../../../Contracts/ViewModels";
|
|||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { updateUserContext } from "../../../UserContext";
|
import { updateUserContext } from "../../../UserContext";
|
||||||
import Explorer from "../../Explorer";
|
|
||||||
import { useDatabases } from "../../useDatabases";
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { useSelectedNode } from "../../useSelectedNode";
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
import { DeleteCollectionConfirmationPane } from "./DeleteCollectionConfirmationPane";
|
import { DeleteCollectionConfirmationPane } from "./DeleteCollectionConfirmationPane";
|
||||||
@@ -53,10 +52,7 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
|
|
||||||
describe("shouldRecordFeedback()", () => {
|
describe("shouldRecordFeedback()", () => {
|
||||||
it("should return true if last collection and database does not have shared throughput else false", () => {
|
it("should return true if last collection and database does not have shared throughput else false", () => {
|
||||||
const fakeExplorer = new Explorer();
|
const wrapper = shallow(<DeleteCollectionConfirmationPane refreshDatabases={() => undefined} />);
|
||||||
fakeExplorer.refreshAllDatabases = () => undefined;
|
|
||||||
|
|
||||||
const wrapper = shallow(<DeleteCollectionConfirmationPane explorer={fakeExplorer} />);
|
|
||||||
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
|
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
|
||||||
|
|
||||||
const database = { id: ko.observable("testDB") } as Database;
|
const database = { id: ko.observable("testDB") } as Database;
|
||||||
@@ -65,11 +61,11 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
database.isDatabaseShared = ko.computed(() => false);
|
database.isDatabaseShared = ko.computed(() => false);
|
||||||
useDatabases.getState().addDatabases([database]);
|
useDatabases.getState().addDatabases([database]);
|
||||||
useSelectedNode.getState().setSelectedNode(database);
|
useSelectedNode.getState().setSelectedNode(database);
|
||||||
wrapper.setProps({ explorer: fakeExplorer });
|
wrapper.setProps({});
|
||||||
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(true);
|
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(true);
|
||||||
|
|
||||||
database.isDatabaseShared = ko.computed(() => true);
|
database.isDatabaseShared = ko.computed(() => true);
|
||||||
wrapper.setProps({ explorer: fakeExplorer });
|
wrapper.setProps({});
|
||||||
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
|
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -77,8 +73,6 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
describe("submit()", () => {
|
describe("submit()", () => {
|
||||||
const selectedCollectionId = "testCol";
|
const selectedCollectionId = "testCol";
|
||||||
const databaseId = "testDatabase";
|
const databaseId = "testDatabase";
|
||||||
const fakeExplorer = {} as Explorer;
|
|
||||||
fakeExplorer.refreshAllDatabases = () => undefined;
|
|
||||||
const database = { id: ko.observable(databaseId) } as Database;
|
const database = { id: ko.observable(databaseId) } as Database;
|
||||||
const collection = {
|
const collection = {
|
||||||
id: ko.observable(selectedCollectionId),
|
id: ko.observable(selectedCollectionId),
|
||||||
@@ -115,7 +109,7 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should call delete collection", () => {
|
it("should call delete collection", () => {
|
||||||
const wrapper = mount(<DeleteCollectionConfirmationPane explorer={fakeExplorer} />);
|
const wrapper = mount(<DeleteCollectionConfirmationPane refreshDatabases={() => undefined} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
|
||||||
expect(wrapper.exists("#confirmCollectionId")).toBe(true);
|
expect(wrapper.exists("#confirmCollectionId")).toBe(true);
|
||||||
@@ -132,7 +126,7 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should record feedback", async () => {
|
it("should record feedback", async () => {
|
||||||
const wrapper = mount(<DeleteCollectionConfirmationPane explorer={fakeExplorer} />);
|
const wrapper = mount(<DeleteCollectionConfirmationPane refreshDatabases={() => undefined} />);
|
||||||
expect(wrapper.exists("#confirmCollectionId")).toBe(true);
|
expect(wrapper.exists("#confirmCollectionId")).toBe(true);
|
||||||
wrapper
|
wrapper
|
||||||
.find("#confirmCollectionId")
|
.find("#confirmCollectionId")
|
||||||
|
|||||||
@@ -6,23 +6,23 @@ import DeleteFeedback from "../../../Common/DeleteFeedback";
|
|||||||
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||||
import { Collection } from "../../../Contracts/ViewModels";
|
import { Collection } from "../../../Contracts/ViewModels";
|
||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
import { DefaultExperienceUtility } from "../../../Shared/DefaultExperienceUtility";
|
import { DefaultExperienceUtility } from "../../../Shared/DefaultExperienceUtility";
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { getCollectionName } from "../../../Utils/APITypeUtils";
|
import { getCollectionName } from "../../../Utils/APITypeUtils";
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
|
||||||
import { useDatabases } from "../../useDatabases";
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { useSelectedNode } from "../../useSelectedNode";
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
export interface DeleteCollectionConfirmationPaneProps {
|
export interface DeleteCollectionConfirmationPaneProps {
|
||||||
explorer: Explorer;
|
refreshDatabases: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectionConfirmationPaneProps> = ({
|
export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectionConfirmationPaneProps> = ({
|
||||||
explorer,
|
refreshDatabases,
|
||||||
}: DeleteCollectionConfirmationPaneProps) => {
|
}: DeleteCollectionConfirmationPaneProps) => {
|
||||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
const [deleteCollectionFeedback, setDeleteCollectionFeedback] = useState<string>("");
|
const [deleteCollectionFeedback, setDeleteCollectionFeedback] = useState<string>("");
|
||||||
@@ -31,8 +31,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
const [isExecuting, setIsExecuting] = useState(false);
|
const [isExecuting, setIsExecuting] = useState(false);
|
||||||
|
|
||||||
const shouldRecordFeedback = (): boolean =>
|
const shouldRecordFeedback = (): boolean =>
|
||||||
useDatabases.getState().isLastCollection() &&
|
useDatabases.getState().isLastCollection() && !useDatabases.getState().findSelectedDatabase()?.isDatabaseShared();
|
||||||
!useSelectedNode.getState().findSelectedDatabase()?.isDatabaseShared();
|
|
||||||
|
|
||||||
const collectionName = getCollectionName().toLocaleLowerCase();
|
const collectionName = getCollectionName().toLocaleLowerCase();
|
||||||
const paneTitle = "Delete " + collectionName;
|
const paneTitle = "Delete " + collectionName;
|
||||||
@@ -63,10 +62,12 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
|
|
||||||
setIsExecuting(false);
|
setIsExecuting(false);
|
||||||
useSelectedNode.getState().setSelectedNode(collection.database);
|
useSelectedNode.getState().setSelectedNode(collection.database);
|
||||||
explorer.tabsManager?.closeTabsByComparator(
|
useTabs
|
||||||
|
.getState()
|
||||||
|
.closeTabsByComparator(
|
||||||
(tab) => tab.node?.id() === collection.id() && (tab.node as Collection).databaseId === collection.databaseId
|
(tab) => tab.node?.id() === collection.id() && (tab.node as Collection).databaseId === collection.databaseId
|
||||||
);
|
);
|
||||||
explorer.refreshAllDatabases();
|
refreshDatabases();
|
||||||
|
|
||||||
TelemetryProcessor.traceSuccess(Action.DeleteCollection, paneInfo, startKey);
|
TelemetryProcessor.traceSuccess(Action.DeleteCollection, paneInfo, startKey);
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,7 @@
|
|||||||
|
|
||||||
exports[`Delete Collection Confirmation Pane submit() should call delete collection 1`] = `
|
exports[`Delete Collection Confirmation Pane submit() should call delete collection 1`] = `
|
||||||
<DeleteCollectionConfirmationPane
|
<DeleteCollectionConfirmationPane
|
||||||
explorer={
|
refreshDatabases={[Function]}
|
||||||
Object {
|
|
||||||
"refreshAllDatabases": [Function],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<RightPaneForm
|
<RightPaneForm
|
||||||
formError=""
|
formError=""
|
||||||
|
|||||||
@@ -10,15 +10,12 @@ import { Collection, Database } from "../../Contracts/ViewModels";
|
|||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
import Explorer from "../Explorer";
|
|
||||||
import { TabsManager } from "../Tabs/TabsManager";
|
|
||||||
import { useDatabases } from "../useDatabases";
|
import { useDatabases } from "../useDatabases";
|
||||||
import { useSelectedNode } from "../useSelectedNode";
|
import { useSelectedNode } from "../useSelectedNode";
|
||||||
import { DeleteDatabaseConfirmationPanel } from "./DeleteDatabaseConfirmationPanel";
|
import { DeleteDatabaseConfirmationPanel } from "./DeleteDatabaseConfirmationPanel";
|
||||||
|
|
||||||
describe("Delete Database Confirmation Pane", () => {
|
describe("Delete Database Confirmation Pane", () => {
|
||||||
const selectedDatabaseId = "testDatabase";
|
const selectedDatabaseId = "testDatabase";
|
||||||
let fakeExplorer: Explorer;
|
|
||||||
let database: Database;
|
let database: Database;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
@@ -37,10 +34,6 @@ describe("Delete Database Confirmation Pane", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fakeExplorer = {} as Explorer;
|
|
||||||
fakeExplorer.refreshAllDatabases = () => undefined;
|
|
||||||
fakeExplorer.tabsManager = new TabsManager();
|
|
||||||
|
|
||||||
database = {} as Database;
|
database = {} as Database;
|
||||||
database.collections = ko.observableArray<Collection>([{ id: ko.observable("testCollection") } as Collection]);
|
database.collections = ko.observableArray<Collection>([{ id: ko.observable("testCollection") } as Collection]);
|
||||||
database.id = ko.observable<string>(selectedDatabaseId);
|
database.id = ko.observable<string>(selectedDatabaseId);
|
||||||
@@ -56,17 +49,17 @@ describe("Delete Database Confirmation Pane", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("shouldRecordFeedback() should return true if last non empty database or is last database that has shared throughput", () => {
|
it("shouldRecordFeedback() should return true if last non empty database or is last database that has shared throughput", () => {
|
||||||
const wrapper = shallow(<DeleteDatabaseConfirmationPanel explorer={fakeExplorer} />);
|
const wrapper = shallow(<DeleteDatabaseConfirmationPanel refreshDatabases={() => undefined} />);
|
||||||
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(true);
|
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(true);
|
||||||
|
|
||||||
useDatabases.getState().addDatabases([database]);
|
useDatabases.getState().addDatabases([database]);
|
||||||
wrapper.setProps({ explorer: fakeExplorer });
|
wrapper.setProps({});
|
||||||
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(false);
|
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(false);
|
||||||
useDatabases.getState().clearDatabases();
|
useDatabases.getState().clearDatabases();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should call delete database", () => {
|
it("Should call delete database", () => {
|
||||||
const wrapper = mount(<DeleteDatabaseConfirmationPanel explorer={fakeExplorer} />);
|
const wrapper = mount(<DeleteDatabaseConfirmationPanel refreshDatabases={() => undefined} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
||||||
|
|
||||||
@@ -81,7 +74,7 @@ describe("Delete Database Confirmation Pane", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should record feedback", async () => {
|
it("should record feedback", async () => {
|
||||||
const wrapper = mount(<DeleteDatabaseConfirmationPanel explorer={fakeExplorer} />);
|
const wrapper = mount(<DeleteDatabaseConfirmationPanel refreshDatabases={() => undefined} />);
|
||||||
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
||||||
wrapper
|
wrapper
|
||||||
.find("#confirmDatabaseId")
|
.find("#confirmDatabaseId")
|
||||||
|
|||||||
@@ -7,23 +7,23 @@ import DeleteFeedback from "../../Common/DeleteFeedback";
|
|||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
import { Collection, Database } from "../../Contracts/ViewModels";
|
import { Collection, Database } from "../../Contracts/ViewModels";
|
||||||
import { useSidePanel } from "../../hooks/useSidePanel";
|
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||||
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../Explorer";
|
|
||||||
import { useDatabases } from "../useDatabases";
|
import { useDatabases } from "../useDatabases";
|
||||||
import { useSelectedNode } from "../useSelectedNode";
|
import { useSelectedNode } from "../useSelectedNode";
|
||||||
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
|
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "./RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "./RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
interface DeleteDatabaseConfirmationPanelProps {
|
interface DeleteDatabaseConfirmationPanelProps {
|
||||||
explorer: Explorer;
|
refreshDatabases: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseConfirmationPanelProps> = ({
|
export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseConfirmationPanelProps> = ({
|
||||||
explorer,
|
refreshDatabases,
|
||||||
}: DeleteDatabaseConfirmationPanelProps): JSX.Element => {
|
}: DeleteDatabaseConfirmationPanelProps): JSX.Element => {
|
||||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
const isLastNonEmptyDatabase = useDatabases((state) => state.isLastNonEmptyDatabase);
|
const isLastNonEmptyDatabase = useDatabases((state) => state.isLastNonEmptyDatabase);
|
||||||
@@ -32,7 +32,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
const [databaseInput, setDatabaseInput] = useState<string>("");
|
const [databaseInput, setDatabaseInput] = useState<string>("");
|
||||||
const [databaseFeedbackInput, setDatabaseFeedbackInput] = useState<string>("");
|
const [databaseFeedbackInput, setDatabaseFeedbackInput] = useState<string>("");
|
||||||
const selectedDatabase: Database = useSelectedNode.getState().findSelectedDatabase();
|
const selectedDatabase: Database = useDatabases.getState().findSelectedDatabase();
|
||||||
|
|
||||||
const submit = async (): Promise<void> => {
|
const submit = async (): Promise<void> => {
|
||||||
if (selectedDatabase?.id() && databaseInput !== selectedDatabase.id()) {
|
if (selectedDatabase?.id() && databaseInput !== selectedDatabase.id()) {
|
||||||
@@ -52,14 +52,17 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
try {
|
try {
|
||||||
await deleteDatabase(selectedDatabase.id());
|
await deleteDatabase(selectedDatabase.id());
|
||||||
closeSidePanel();
|
closeSidePanel();
|
||||||
explorer.refreshAllDatabases();
|
refreshDatabases();
|
||||||
explorer.tabsManager.closeTabsByComparator((tab) => tab.node?.id() === selectedDatabase.id());
|
useTabs.getState().closeTabsByComparator((tab) => tab.node?.id() === selectedDatabase.id());
|
||||||
useSelectedNode.getState().setSelectedNode(undefined);
|
useSelectedNode.getState().setSelectedNode(undefined);
|
||||||
selectedDatabase
|
selectedDatabase
|
||||||
.collections()
|
.collections()
|
||||||
.forEach((collection: Collection) =>
|
.forEach((collection: Collection) =>
|
||||||
explorer.tabsManager.closeTabsByComparator(
|
useTabs
|
||||||
(tab) => tab.node?.id() === collection.id() && (tab.node as Collection).databaseId === collection.databaseId
|
.getState()
|
||||||
|
.closeTabsByComparator(
|
||||||
|
(tab) =>
|
||||||
|
tab.node?.id() === collection.id() && (tab.node as Collection).databaseId === collection.databaseId
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
TelemetryProcessor.traceSuccess(
|
TelemetryProcessor.traceSuccess(
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
|||||||
handleError(error, "GitHubReposPane/submit", "Failed to save pinned repos");
|
handleError(error, "GitHubReposPane/submit", "Failed to save pinned repos");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
useSidePanel.getState().closeSidePanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public resetData(): void {
|
public resetData(): void {
|
||||||
@@ -144,11 +145,18 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
|||||||
|
|
||||||
private setup(forceShowConnectToGitHub = false): void {
|
private setup(forceShowConnectToGitHub = false): void {
|
||||||
forceShowConnectToGitHub || !this.props.explorer.notebookManager?.gitHubOAuthService.isLoggedIn()
|
forceShowConnectToGitHub || !this.props.explorer.notebookManager?.gitHubOAuthService.isLoggedIn()
|
||||||
? this.setupForConnectToGitHub()
|
? this.setupForConnectToGitHub(forceShowConnectToGitHub)
|
||||||
: this.setupForManageRepos();
|
: this.setupForManageRepos();
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupForConnectToGitHub(): void {
|
private setupForConnectToGitHub(forceShowConnectToGitHub: boolean): void {
|
||||||
|
if (forceShowConnectToGitHub) {
|
||||||
|
const newState = { ...this.state.gitHubReposState };
|
||||||
|
newState.showAuthorizeAccess = forceShowConnectToGitHub;
|
||||||
|
this.setState({
|
||||||
|
gitHubReposState: newState,
|
||||||
|
});
|
||||||
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
isExecuting: false,
|
isExecuting: false,
|
||||||
});
|
});
|
||||||
@@ -368,14 +376,17 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
|||||||
isLoading: true,
|
isLoading: true,
|
||||||
loadMore: (): Promise<void> => this.loadMoreBranches(item.repo),
|
loadMore: (): Promise<void> => this.loadMoreBranches(item.repo),
|
||||||
};
|
};
|
||||||
|
this.loadMoreBranches(item.repo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
gitHubReposState: {
|
gitHubReposState: {
|
||||||
...this.state.gitHubReposState,
|
...this.state.gitHubReposState,
|
||||||
reposListProps: {
|
reposListProps: {
|
||||||
...this.state.gitHubReposState.reposListProps,
|
...this.state.gitHubReposState.reposListProps,
|
||||||
branchesProps: {
|
branchesProps: {
|
||||||
...this.state.gitHubReposState.reposListProps.branchesProps,
|
...this.branchesProps,
|
||||||
[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)]: this.branchesProps[item.key],
|
|
||||||
},
|
},
|
||||||
pinnedReposProps: {
|
pinnedReposProps: {
|
||||||
repos: this.pinnedReposProps.repos,
|
repos: this.pinnedReposProps.repos,
|
||||||
@@ -387,27 +398,6 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.loadMoreBranches(item.repo);
|
|
||||||
} else {
|
|
||||||
if (this.isAddedRepo === false) {
|
|
||||||
this.setState({
|
|
||||||
gitHubReposState: {
|
|
||||||
...this.state.gitHubReposState,
|
|
||||||
reposListProps: {
|
|
||||||
...this.state.gitHubReposState.reposListProps,
|
|
||||||
pinnedReposProps: {
|
|
||||||
repos: this.pinnedReposProps.repos,
|
|
||||||
},
|
|
||||||
unpinnedReposProps: {
|
|
||||||
...this.state.gitHubReposState.reposListProps.unpinnedReposProps,
|
|
||||||
repos: this.unpinnedReposProps.repos,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.isAddedRepo = false;
|
this.isAddedRepo = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,17 +19,8 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
|||||||
"container": Explorer {
|
"container": Explorer {
|
||||||
"_isInitializingNotebooks": false,
|
"_isInitializingNotebooks": false,
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"isAccountReady": [Function],
|
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
"isNotebookEnabled": [Function],
|
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
|
||||||
"isSchemaEnabled": [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],
|
||||||
@@ -37,21 +28,11 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
|||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
},
|
},
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"resourceTokenCollection": [Function],
|
|
||||||
"resourceTree": ResourceTreeAdapter {
|
"resourceTree": ResourceTreeAdapter {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"copyNotebook": [Function],
|
"copyNotebook": [Function],
|
||||||
"parameters": [Function],
|
"parameters": [Function],
|
||||||
},
|
},
|
||||||
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
|
||||||
"container": [Circular],
|
|
||||||
"parameters": [Function],
|
|
||||||
},
|
|
||||||
"sparkClusterConnectionInfo": [Function],
|
|
||||||
"tabsManager": TabsManager {
|
|
||||||
"activeTab": [Function],
|
|
||||||
"openedTabs": [Function],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"getRepo": [Function],
|
"getRepo": [Function],
|
||||||
"pinRepo": [Function],
|
"pinRepo": [Function],
|
||||||
|
|||||||
@@ -150,9 +150,6 @@
|
|||||||
.backImageIcon {
|
.backImageIcon {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
.entityValueTextField {
|
|
||||||
margin: 24px;
|
|
||||||
}
|
|
||||||
.addEntityDatePicker {
|
.addEntityDatePicker {
|
||||||
max-width: 145px;
|
max-width: 145px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { useNotificationConsole } from "../../hooks/useNotificationConsole";
|
|||||||
import { useSidePanel } from "../../hooks/useSidePanel";
|
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||||
|
|
||||||
export interface PanelContainerProps {
|
export interface PanelContainerProps {
|
||||||
headerText: string;
|
headerText?: string;
|
||||||
panelContent: JSX.Element;
|
panelContent?: JSX.Element;
|
||||||
isConsoleExpanded: boolean;
|
isConsoleExpanded: boolean;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
panelWidth?: string;
|
panelWidth?: string;
|
||||||
@@ -66,8 +66,8 @@ export class PanelContainerComponent extends React.Component<PanelContainerProps
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onDissmiss = (ev?: React.SyntheticEvent<HTMLElement>): void => {
|
private onDissmiss = (ev?: KeyboardEvent | React.SyntheticEvent<HTMLElement>): void => {
|
||||||
if ((ev.target as HTMLElement).id === "notificationConsoleHeader") {
|
if (ev && (ev.target as HTMLElement).id === "notificationConsoleHeader") {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
} else {
|
} else {
|
||||||
useSidePanel.getState().closeSidePanel();
|
useSidePanel.getState().closeSidePanel();
|
||||||
@@ -85,11 +85,12 @@ export class PanelContainerComponent extends React.Component<PanelContainerProps
|
|||||||
|
|
||||||
export const SidePanel: React.FC = () => {
|
export const SidePanel: React.FC = () => {
|
||||||
const isConsoleExpanded = useNotificationConsole((state) => state.isExpanded);
|
const isConsoleExpanded = useNotificationConsole((state) => state.isExpanded);
|
||||||
const { isOpen, panelContent, headerText } = useSidePanel((state) => {
|
const { isOpen, panelContent, panelWidth, headerText } = useSidePanel((state) => {
|
||||||
return {
|
return {
|
||||||
isOpen: state.isOpen,
|
isOpen: state.isOpen,
|
||||||
panelContent: state.panelContent,
|
panelContent: state.panelContent,
|
||||||
headerText: state.headerText,
|
headerText: state.headerText,
|
||||||
|
panelWidth: state.panelWidth,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
// TODO Refactor PanelContainerComponent into a functional component and remove this wrapper
|
// TODO Refactor PanelContainerComponent into a functional component and remove this wrapper
|
||||||
@@ -100,6 +101,7 @@ export const SidePanel: React.FC = () => {
|
|||||||
panelContent={panelContent}
|
panelContent={panelContent}
|
||||||
headerText={headerText}
|
headerText={headerText}
|
||||||
isConsoleExpanded={isConsoleExpanded}
|
isConsoleExpanded={isConsoleExpanded}
|
||||||
|
panelWidth={panelWidth}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Areas, SavedQueries } from "../../../Common/Constants";
|
|||||||
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||||
import { Query } from "../../../Contracts/DataModels";
|
import { Query } from "../../../Contracts/DataModels";
|
||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||||
@@ -34,7 +35,7 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({ explorer
|
|||||||
logConsoleError("Failed to save query: account not setup to save queries");
|
logConsoleError("Failed to save query: account not setup to save queries");
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryTab = explorer && (explorer.tabsManager.activeTab() as NewQueryTab);
|
const queryTab = useTabs.getState().activeTab as NewQueryTab;
|
||||||
const query: string = queryTab && queryTab.iTabAccessor.onSaveClickEvent();
|
const query: string = queryTab && queryTab.iTabAccessor.onSaveClickEvent();
|
||||||
|
|
||||||
if (!queryName || queryName.length === 0) {
|
if (!queryName || queryName.length === 0) {
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export const SetupNoteBooksPanel: FunctionComponent<SetupNoteBooksPanelProps> =
|
|||||||
userContext.databaseAccount.name,
|
userContext.databaseAccount.name,
|
||||||
"default"
|
"default"
|
||||||
);
|
);
|
||||||
explorer.isAccountReady.valueHasMutated(); // re-trigger init notebooks
|
explorer.refreshExplorer();
|
||||||
|
|
||||||
closeSidePanel();
|
closeSidePanel();
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { TextField } from "@fluentui/react";
|
import { TextField } from "@fluentui/react";
|
||||||
import React, { FormEvent, FunctionComponent, useState } from "react";
|
import React, { FormEvent, FunctionComponent, useState } from "react";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
|
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
|
||||||
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
|
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
|
||||||
import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
|
import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
|
||||||
import NotebookV2Tab from "../../Tabs/NotebookV2Tab";
|
import NotebookV2Tab from "../../Tabs/NotebookV2Tab";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
export interface StringInputPanelProps {
|
export interface StringInputPanelProps {
|
||||||
explorer: Explorer;
|
|
||||||
closePanel: () => void;
|
closePanel: () => void;
|
||||||
errorMessage: string;
|
errorMessage: string;
|
||||||
inProgressMessage: string;
|
inProgressMessage: string;
|
||||||
@@ -23,7 +22,6 @@ export interface StringInputPanelProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
|
export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
|
||||||
explorer: container,
|
|
||||||
closePanel,
|
closePanel,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
inProgressMessage,
|
inProgressMessage,
|
||||||
@@ -55,7 +53,9 @@ export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
|
|||||||
logConsoleInfo(`${successMessage}: ${stringInput}`);
|
logConsoleInfo(`${successMessage}: ${stringInput}`);
|
||||||
const originalPath = notebookFile.path;
|
const originalPath = notebookFile.path;
|
||||||
|
|
||||||
const notebookTabs = container.tabsManager.getTabs(
|
const notebookTabs = useTabs
|
||||||
|
.getState()
|
||||||
|
.getTabs(
|
||||||
ViewModels.CollectionTabKind.NotebookV2,
|
ViewModels.CollectionTabKind.NotebookV2,
|
||||||
(tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath)
|
(tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,17 +9,8 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
Explorer {
|
Explorer {
|
||||||
"_isInitializingNotebooks": false,
|
"_isInitializingNotebooks": false,
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"isAccountReady": [Function],
|
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
"isNotebookEnabled": [Function],
|
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
|
||||||
"isSchemaEnabled": [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],
|
||||||
@@ -27,21 +18,11 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
},
|
},
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"resourceTokenCollection": [Function],
|
|
||||||
"resourceTree": ResourceTreeAdapter {
|
"resourceTree": ResourceTreeAdapter {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"copyNotebook": [Function],
|
"copyNotebook": [Function],
|
||||||
"parameters": [Function],
|
"parameters": [Function],
|
||||||
},
|
},
|
||||||
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
|
||||||
"container": [Circular],
|
|
||||||
"parameters": [Function],
|
|
||||||
},
|
|
||||||
"sparkClusterConnectionInfo": [Function],
|
|
||||||
"tabsManager": TabsManager {
|
|
||||||
"activeTab": [Function],
|
|
||||||
"openedTabs": [Function],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inProgressMessage="Creating directory "
|
inProgressMessage="Creating directory "
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { IDropdownOption, Image, IPanelProps, IRenderFunction, Label, Stack, Text, TextField } from "@fluentui/react";
|
import { IDropdownOption, Image, Label, Stack, Text, TextField } from "@fluentui/react";
|
||||||
import { useBoolean } from "@fluentui/react-hooks";
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
||||||
import RevertBackIcon from "../../../../images/RevertBack.svg";
|
import RevertBackIcon from "../../../../images/RevertBack.svg";
|
||||||
|
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
import { TableEntity } from "../../../Common/TableEntity";
|
import { TableEntity } from "../../../Common/TableEntity";
|
||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import * as TableConstants from "../../Tables/Constants";
|
import * as TableConstants from "../../Tables/Constants";
|
||||||
import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities";
|
import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities";
|
||||||
@@ -14,8 +14,8 @@ import * as Entities from "../../Tables/Entities";
|
|||||||
import { CassandraAPIDataClient, CassandraTableKey, TableDataClient } from "../../Tables/TableDataClient";
|
import { CassandraAPIDataClient, CassandraTableKey, TableDataClient } from "../../Tables/TableDataClient";
|
||||||
import * as TableEntityProcessor from "../../Tables/TableEntityProcessor";
|
import * as TableEntityProcessor from "../../Tables/TableEntityProcessor";
|
||||||
import * as Utilities from "../../Tables/Utilities";
|
import * as Utilities from "../../Tables/Utilities";
|
||||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
import { NewQueryTablesTab } from "../../Tabs/QueryTablesTab/NewQueryTablesTab";
|
||||||
import { PanelContainerComponent } from "../PanelContainerComponent";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
import {
|
import {
|
||||||
attributeNameLabel,
|
attributeNameLabel,
|
||||||
attributeValueLabel,
|
attributeValueLabel,
|
||||||
@@ -30,15 +30,13 @@ import {
|
|||||||
getCassandraDefaultEntities,
|
getCassandraDefaultEntities,
|
||||||
getDefaultEntities,
|
getDefaultEntities,
|
||||||
getEntityValuePlaceholder,
|
getEntityValuePlaceholder,
|
||||||
getPanelTitle,
|
|
||||||
imageProps,
|
imageProps,
|
||||||
isValidEntities,
|
|
||||||
options,
|
options,
|
||||||
} from "./Validators/EntityTableHelper";
|
} from "./Validators/EntityTableHelper";
|
||||||
|
|
||||||
interface AddTableEntityPanelProps {
|
interface AddTableEntityPanelProps {
|
||||||
tableDataClient: TableDataClient;
|
tableDataClient: TableDataClient;
|
||||||
queryTablesTab: QueryTablesTab;
|
queryTablesTab: NewQueryTablesTab;
|
||||||
tableEntityListViewModel: TableEntityListViewModel;
|
tableEntityListViewModel: TableEntityListViewModel;
|
||||||
cassandraApiClient: CassandraAPIDataClient;
|
cassandraApiClient: CassandraAPIDataClient;
|
||||||
}
|
}
|
||||||
@@ -61,7 +59,6 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
|||||||
tableEntityListViewModel,
|
tableEntityListViewModel,
|
||||||
cassandraApiClient,
|
cassandraApiClient,
|
||||||
}: AddTableEntityPanelProps): JSX.Element => {
|
}: AddTableEntityPanelProps): JSX.Element => {
|
||||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
|
||||||
const [entities, setEntities] = useState<EntityRowType[]>([]);
|
const [entities, setEntities] = useState<EntityRowType[]>([]);
|
||||||
const [selectedRow, setSelectedRow] = useState<number>(0);
|
const [selectedRow, setSelectedRow] = useState<number>(0);
|
||||||
const [entityAttributeValue, setEntityAttributeValue] = useState<string>("");
|
const [entityAttributeValue, setEntityAttributeValue] = useState<string>("");
|
||||||
@@ -70,6 +67,8 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
|||||||
isEntityValuePanelOpen,
|
isEntityValuePanelOpen,
|
||||||
{ setTrue: setIsEntityValuePanelTrue, setFalse: setIsEntityValuePanelFalse },
|
{ setTrue: setIsEntityValuePanelTrue, setFalse: setIsEntityValuePanelFalse },
|
||||||
] = useBoolean(false);
|
] = useBoolean(false);
|
||||||
|
const [formError, setFormError] = useState<string>("");
|
||||||
|
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||||
|
|
||||||
/* Get default and previous saved entity headers */
|
/* Get default and previous saved entity headers */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -98,19 +97,36 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* Add new entity attribute */
|
/* Add new entity attribute */
|
||||||
const submit = async (event: React.FormEvent<HTMLInputElement>): Promise<void> => {
|
const onSubmit = async (): Promise<void> => {
|
||||||
if (!isValidEntities(entities)) {
|
for (let i = 0; i < entities.length; i++) {
|
||||||
return undefined;
|
const { property, type } = entities[i];
|
||||||
|
if (property === "" || property === undefined) {
|
||||||
|
setFormError(`Property name cannot be empty. Please enter a property name`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
|
if (!type) {
|
||||||
|
setFormError(`Property type cannot be empty. Please select a type from the dropdown for property ${property}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsExecuting(true);
|
||||||
const entity: Entities.ITableEntity = entityFromAttributes(entities);
|
const entity: Entities.ITableEntity = entityFromAttributes(entities);
|
||||||
const newEntity: Entities.ITableEntity = await tableDataClient.createDocument(queryTablesTab.collection, entity);
|
const newEntity: Entities.ITableEntity = await tableDataClient.createDocument(queryTablesTab.collection, entity);
|
||||||
|
try {
|
||||||
await tableEntityListViewModel.addEntityToCache(newEntity);
|
await tableEntityListViewModel.addEntityToCache(newEntity);
|
||||||
if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) {
|
if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) {
|
||||||
tableEntityListViewModel.redrawTableThrottled();
|
tableEntityListViewModel.redrawTableThrottled();
|
||||||
}
|
}
|
||||||
closeSidePanel();
|
} catch (error) {
|
||||||
|
const errorMessage = getErrorMessage(error);
|
||||||
|
setFormError(errorMessage);
|
||||||
|
handleError(errorMessage, "AddTableRow");
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
setIsExecuting(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const tryInsertNewHeaders = (viewModel: TableEntityListViewModel, newEntity: Entities.ITableEntity): boolean => {
|
const tryInsertNewHeaders = (viewModel: TableEntityListViewModel, newEntity: Entities.ITableEntity): boolean => {
|
||||||
@@ -200,10 +216,35 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
|||||||
setIsEntityValuePanelTrue();
|
setIsEntityValuePanelTrue();
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderPanelContent = (): JSX.Element => {
|
if (isEntityValuePanelOpen) {
|
||||||
return (
|
return (
|
||||||
<form className="panelFormWrapper">
|
<Stack style={{ padding: "20px 34px" }}>
|
||||||
<div className="panelFormWrapper">
|
<Stack horizontal {...columnProps}>
|
||||||
|
<Image {...backImageProps} src={RevertBackIcon} alt="back" onClick={() => setIsEntityValuePanelFalse()} />
|
||||||
|
<Label>{entityAttributeProperty}</Label>
|
||||||
|
</Stack>
|
||||||
|
<TextField
|
||||||
|
multiline
|
||||||
|
rows={5}
|
||||||
|
value={entityAttributeValue}
|
||||||
|
onChange={(event, newInput?: string) => {
|
||||||
|
entityChange(newInput, selectedRow, "value");
|
||||||
|
setEntityAttributeValue(newInput);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const props: RightPaneFormProps = {
|
||||||
|
formError,
|
||||||
|
isExecuting,
|
||||||
|
submitButtonText: getButtonLabel(userContext.apiType),
|
||||||
|
onSubmit,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RightPaneForm {...props}>
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
{entities.map((entity, index) => {
|
{entities.map((entity, index) => {
|
||||||
return (
|
return (
|
||||||
@@ -249,61 +290,6 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
|||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="paneFooter">
|
</RightPaneForm>
|
||||||
<div className="leftpanel-okbut">
|
|
||||||
<input
|
|
||||||
type="submit"
|
|
||||||
onClick={submit}
|
|
||||||
className="genericPaneSubmitBtn"
|
|
||||||
value={getButtonLabel(userContext.apiType)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onRenderNavigationContent: IRenderFunction<IPanelProps> = () => {
|
|
||||||
return (
|
|
||||||
<Stack horizontal {...columnProps}>
|
|
||||||
<Image {...backImageProps} src={RevertBackIcon} alt="back" onClick={() => setIsEntityValuePanelFalse()} />
|
|
||||||
<Label>{entityAttributeProperty}</Label>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isEntityValuePanelOpen) {
|
|
||||||
return (
|
|
||||||
<PanelContainerComponent
|
|
||||||
headerText=""
|
|
||||||
onRenderNavigationContent={onRenderNavigationContent}
|
|
||||||
panelWidth="700px"
|
|
||||||
isOpen={true}
|
|
||||||
panelContent={
|
|
||||||
<TextField
|
|
||||||
multiline
|
|
||||||
rows={5}
|
|
||||||
className="entityValueTextField"
|
|
||||||
value={entityAttributeValue}
|
|
||||||
onChange={(event, newInput?: string) => {
|
|
||||||
entityChange(newInput, selectedRow, "value");
|
|
||||||
setEntityAttributeValue(newInput);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
isConsoleExpanded={false}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PanelContainerComponent
|
|
||||||
headerText={getPanelTitle(userContext.apiType)}
|
|
||||||
panelWidth="700px"
|
|
||||||
isOpen={true}
|
|
||||||
panelContent={renderPanelContent()}
|
|
||||||
isConsoleExpanded={false}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { IDropdownOption, Image, IPanelProps, IRenderFunction, Label, Stack, Text, TextField } from "@fluentui/react";
|
import { IDropdownOption, Image, Label, Stack, Text, TextField } from "@fluentui/react";
|
||||||
import { useBoolean } from "@fluentui/react-hooks";
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
||||||
import RevertBackIcon from "../../../../images/RevertBack.svg";
|
import RevertBackIcon from "../../../../images/RevertBack.svg";
|
||||||
|
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
import { TableEntity } from "../../../Common/TableEntity";
|
import { TableEntity } from "../../../Common/TableEntity";
|
||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import * as TableConstants from "../../Tables/Constants";
|
import * as TableConstants from "../../Tables/Constants";
|
||||||
import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities";
|
import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities";
|
||||||
@@ -13,8 +13,8 @@ import TableEntityListViewModel from "../../Tables/DataTable/TableEntityListView
|
|||||||
import * as Entities from "../../Tables/Entities";
|
import * as Entities from "../../Tables/Entities";
|
||||||
import { CassandraAPIDataClient, TableDataClient } from "../../Tables/TableDataClient";
|
import { CassandraAPIDataClient, TableDataClient } from "../../Tables/TableDataClient";
|
||||||
import * as TableEntityProcessor from "../../Tables/TableEntityProcessor";
|
import * as TableEntityProcessor from "../../Tables/TableEntityProcessor";
|
||||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
import { NewQueryTablesTab } from "../../Tabs/QueryTablesTab/NewQueryTablesTab";
|
||||||
import { PanelContainerComponent } from "../PanelContainerComponent";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
import {
|
import {
|
||||||
attributeNameLabel,
|
attributeNameLabel,
|
||||||
attributeValueLabel,
|
attributeValueLabel,
|
||||||
@@ -29,13 +29,12 @@ import {
|
|||||||
getEntityValuePlaceholder,
|
getEntityValuePlaceholder,
|
||||||
getFormattedTime,
|
getFormattedTime,
|
||||||
imageProps,
|
imageProps,
|
||||||
isValidEntities,
|
|
||||||
options,
|
options,
|
||||||
} from "./Validators/EntityTableHelper";
|
} from "./Validators/EntityTableHelper";
|
||||||
|
|
||||||
interface EditTableEntityPanelProps {
|
interface EditTableEntityPanelProps {
|
||||||
tableDataClient: TableDataClient;
|
tableDataClient: TableDataClient;
|
||||||
queryTablesTab: QueryTablesTab;
|
queryTablesTab: NewQueryTablesTab;
|
||||||
tableEntityListViewModel: TableEntityListViewModel;
|
tableEntityListViewModel: TableEntityListViewModel;
|
||||||
cassandraApiClient: CassandraAPIDataClient;
|
cassandraApiClient: CassandraAPIDataClient;
|
||||||
}
|
}
|
||||||
@@ -59,12 +58,13 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
|
|||||||
tableEntityListViewModel,
|
tableEntityListViewModel,
|
||||||
cassandraApiClient,
|
cassandraApiClient,
|
||||||
}: EditTableEntityPanelProps): JSX.Element => {
|
}: EditTableEntityPanelProps): JSX.Element => {
|
||||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
|
||||||
const [entities, setEntities] = useState<EntityRowType[]>([]);
|
const [entities, setEntities] = useState<EntityRowType[]>([]);
|
||||||
const [selectedRow, setSelectedRow] = useState<number>(0);
|
const [selectedRow, setSelectedRow] = useState<number>(0);
|
||||||
const [entityAttributeValue, setEntityAttributeValue] = useState<string>("");
|
const [entityAttributeValue, setEntityAttributeValue] = useState<string>("");
|
||||||
const [originalDocument, setOriginalDocument] = useState<Entities.ITableEntity>({});
|
const [originalDocument, setOriginalDocument] = useState<Entities.ITableEntity>({});
|
||||||
const [entityAttributeProperty, setEntityAttributeProperty] = useState<string>("");
|
const [entityAttributeProperty, setEntityAttributeProperty] = useState<string>("");
|
||||||
|
const [formError, setFormError] = useState<string>("");
|
||||||
|
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
isEntityValuePanelOpen,
|
isEntityValuePanelOpen,
|
||||||
@@ -190,14 +190,26 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
|
|||||||
return displayValue;
|
return displayValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
const submit = async (event: React.FormEvent<HTMLInputElement>): Promise<void> => {
|
const onSubmit = async (): Promise<void> => {
|
||||||
if (!isValidEntities(entities)) {
|
for (let i = 0; i < entities.length; i++) {
|
||||||
return undefined;
|
const { property, type } = entities[i];
|
||||||
|
if (property === "" || property === undefined) {
|
||||||
|
setFormError(`Property name cannot be empty. Please enter a property name`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
|
||||||
|
if (!type) {
|
||||||
|
setFormError(`Property type cannot be empty. Please select a type from the dropdown for property ${property}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsExecuting(true);
|
||||||
const entity: Entities.ITableEntity = entityFromAttributes(entities);
|
const entity: Entities.ITableEntity = entityFromAttributes(entities);
|
||||||
const newTableDataClient = userContext.apiType === "Cassandra" ? cassandraApiClient : tableDataClient;
|
const newTableDataClient = userContext.apiType === "Cassandra" ? cassandraApiClient : tableDataClient;
|
||||||
const originalDocumentData = userContext.apiType === "Cassandra" ? originalDocument[0] : originalDocument;
|
const originalDocumentData = userContext.apiType === "Cassandra" ? originalDocument[0] : originalDocument;
|
||||||
|
|
||||||
|
try {
|
||||||
const newEntity: Entities.ITableEntity = await newTableDataClient.updateDocument(
|
const newEntity: Entities.ITableEntity = await newTableDataClient.updateDocument(
|
||||||
queryTablesTab.collection,
|
queryTablesTab.collection,
|
||||||
originalDocumentData,
|
originalDocumentData,
|
||||||
@@ -209,7 +221,13 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
|
|||||||
}
|
}
|
||||||
tableEntityListViewModel.selected.removeAll();
|
tableEntityListViewModel.selected.removeAll();
|
||||||
tableEntityListViewModel.selected.push(newEntity);
|
tableEntityListViewModel.selected.push(newEntity);
|
||||||
closeSidePanel();
|
} catch (error) {
|
||||||
|
const errorMessage = getErrorMessage(error);
|
||||||
|
handleError(errorMessage, "EditTableRow");
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
setIsExecuting(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const tryInsertNewHeaders = (viewModel: TableEntityListViewModel, newEntity: Entities.ITableEntity): boolean => {
|
const tryInsertNewHeaders = (viewModel: TableEntityListViewModel, newEntity: Entities.ITableEntity): boolean => {
|
||||||
@@ -299,10 +317,35 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
|
|||||||
setIsEntityValuePanelTrue();
|
setIsEntityValuePanelTrue();
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderPanelContent = (): JSX.Element => {
|
if (isEntityValuePanelOpen) {
|
||||||
return (
|
return (
|
||||||
<form className="panelFormWrapper">
|
<Stack style={{ padding: "20px 34px" }}>
|
||||||
<div className="panelFormWrapper">
|
<Stack horizontal {...columnProps}>
|
||||||
|
<Image {...backImageProps} src={RevertBackIcon} alt="back" onClick={() => setIsEntityValuePanelFalse()} />
|
||||||
|
<Label>{entityAttributeProperty}</Label>
|
||||||
|
</Stack>
|
||||||
|
<TextField
|
||||||
|
multiline
|
||||||
|
rows={5}
|
||||||
|
value={entityAttributeValue}
|
||||||
|
onChange={(event, newInput?: string) => {
|
||||||
|
setEntityAttributeValue(newInput);
|
||||||
|
entityChange(newInput, selectedRow, "value");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const props: RightPaneFormProps = {
|
||||||
|
formError,
|
||||||
|
isExecuting,
|
||||||
|
submitButtonText: "Update",
|
||||||
|
onSubmit,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RightPaneForm {...props}>
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
{entities.map((entity, index) => {
|
{entities.map((entity, index) => {
|
||||||
return (
|
return (
|
||||||
@@ -349,59 +392,6 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
|
|||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{renderPanelFooter()}
|
</RightPaneForm>
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderPanelFooter = (): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<div className="paneFooter">
|
|
||||||
<div className="leftpanel-okbut">
|
|
||||||
<input type="submit" onClick={submit} className="genericPaneSubmitBtn" value="Update Entity" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onRenderNavigationContent: IRenderFunction<IPanelProps> = () => (
|
|
||||||
<Stack horizontal {...columnProps}>
|
|
||||||
<Image {...backImageProps} src={RevertBackIcon} alt="back" onClick={() => setIsEntityValuePanelFalse()} />
|
|
||||||
<Label>{entityAttributeProperty}</Label>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
if (isEntityValuePanelOpen) {
|
|
||||||
return (
|
|
||||||
<PanelContainerComponent
|
|
||||||
headerText=""
|
|
||||||
onRenderNavigationContent={onRenderNavigationContent}
|
|
||||||
panelWidth="700px"
|
|
||||||
isOpen={true}
|
|
||||||
panelContent={
|
|
||||||
<TextField
|
|
||||||
multiline
|
|
||||||
rows={5}
|
|
||||||
className="entityValueTextField"
|
|
||||||
value={entityAttributeValue}
|
|
||||||
onChange={(event, newInput?: string) => {
|
|
||||||
setEntityAttributeValue(newInput);
|
|
||||||
entityChange(newInput, selectedRow, "value");
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
isConsoleExpanded={false}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PanelContainerComponent
|
|
||||||
headerText="Edit Table Entity"
|
|
||||||
panelWidth="700px"
|
|
||||||
isOpen={true}
|
|
||||||
panelContent={renderPanelContent()}
|
|
||||||
isConsoleExpanded={false}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export const int64Placeholder = "Enter a signed 64-bit integer, in the range (-2
|
|||||||
|
|
||||||
export const columnProps: Partial<IStackProps> = {
|
export const columnProps: Partial<IStackProps> = {
|
||||||
tokens: { childrenGap: 10 },
|
tokens: { childrenGap: 10 },
|
||||||
styles: { root: { width: 680 } },
|
styles: { root: { marginBottom: 8 } },
|
||||||
};
|
};
|
||||||
|
|
||||||
// helper functions
|
// helper functions
|
||||||
@@ -134,8 +134,8 @@ export const getEntityValuePlaceholder = (entityType: string | number): string =
|
|||||||
|
|
||||||
export const isValidEntities = (entities: EntityRowType[]): boolean => {
|
export const isValidEntities = (entities: EntityRowType[]): boolean => {
|
||||||
for (let i = 0; i < entities.length; i++) {
|
for (let i = 0; i < entities.length; i++) {
|
||||||
const { property } = entities[i];
|
const { property, type } = entities[i];
|
||||||
if (property === "" || property === undefined) {
|
if (property === "" || property === undefined || !type) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,13 +170,6 @@ export const getDefaultEntities = (headers: string[], entityTypes: EntityType):
|
|||||||
return defaultEntities;
|
return defaultEntities;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPanelTitle = (apiType: string): string => {
|
|
||||||
if (apiType === "Cassandra") {
|
|
||||||
return "Add Table Row";
|
|
||||||
}
|
|
||||||
return "Add Table Row";
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAddButtonLabel = (apiType: string): string => {
|
export const getAddButtonLabel = (apiType: string): string => {
|
||||||
if (apiType === "Cassandra") {
|
if (apiType === "Cassandra") {
|
||||||
return "Add Row";
|
return "Add Row";
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -2,15 +2,7 @@
|
|||||||
|
|
||||||
exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||||
<DeleteDatabaseConfirmationPanel
|
<DeleteDatabaseConfirmationPanel
|
||||||
explorer={
|
refreshDatabases={[Function]}
|
||||||
Object {
|
|
||||||
"refreshAllDatabases": [Function],
|
|
||||||
"tabsManager": TabsManager {
|
|
||||||
"activeTab": [Function],
|
|
||||||
"openedTabs": [Function],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<RightPaneForm
|
<RightPaneForm
|
||||||
formError=""
|
formError=""
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
|
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { TabsManager } from "../Tabs/TabsManager";
|
|
||||||
import { SplashScreen } from "./SplashScreen";
|
import { SplashScreen } from "./SplashScreen";
|
||||||
jest.mock("../Explorer");
|
jest.mock("../Explorer");
|
||||||
|
|
||||||
const createExplorer = () => {
|
const createExplorer = () => {
|
||||||
const mock = new Explorer();
|
const mock = new Explorer();
|
||||||
mock.isNotebookEnabled = ko.observable(false);
|
|
||||||
mock.tabsManager = new TabsManager();
|
|
||||||
return mock as jest.Mocked<Explorer>;
|
return mock as jest.Mocked<Explorer>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,16 @@ import CollectionIcon from "../../../images/tree-collection.svg";
|
|||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
|
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { getCollectionName, getDatabaseName } from "../../Utils/APITypeUtils";
|
import { getCollectionName, getDatabaseName } from "../../Utils/APITypeUtils";
|
||||||
import { FeaturePanelLauncher } from "../Controls/FeaturePanel/FeaturePanelLauncher";
|
import { FeaturePanelLauncher } from "../Controls/FeaturePanel/FeaturePanelLauncher";
|
||||||
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
|
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity";
|
import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity";
|
||||||
|
import { useNotebook } from "../Notebook/useNotebook";
|
||||||
|
import { AddDatabasePanel } from "../Panes/AddDatabasePanel/AddDatabasePanel";
|
||||||
|
import { BrowseQueriesPane } from "../Panes/BrowseQueriesPane/BrowseQueriesPane";
|
||||||
import { useDatabases } from "../useDatabases";
|
import { useDatabases } from "../useDatabases";
|
||||||
import { useSelectedNode } from "../useSelectedNode";
|
import { useSelectedNode } from "../useSelectedNode";
|
||||||
|
|
||||||
@@ -61,8 +65,13 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
|
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
this.subscriptions.push(
|
this.subscriptions.push(
|
||||||
{ dispose: useSelectedNode.subscribe(() => this.setState({})) },
|
{
|
||||||
this.container.isNotebookEnabled.subscribe(() => this.setState({}))
|
dispose: useNotebook.subscribe(
|
||||||
|
() => this.setState({}),
|
||||||
|
(state) => state.isNotebookEnabled
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ dispose: useSelectedNode.subscribe(() => this.setState({})) }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +176,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
<li>
|
<li>
|
||||||
<a role="link" href={SplashScreen.seeMoreItemUrl} target="_blank" tabIndex={0}>
|
<a role="link" href={SplashScreen.seeMoreItemUrl} rel="noreferrer" target="_blank" tabIndex={0}>
|
||||||
{SplashScreen.seeMoreItemTitle}
|
{SplashScreen.seeMoreItemTitle}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -210,7 +219,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.container.isNotebookEnabled()) {
|
if (useNotebook.getState().isNotebookEnabled) {
|
||||||
heroes.push({
|
heroes.push({
|
||||||
iconSrc: NewNotebookIcon,
|
iconSrc: NewNotebookIcon,
|
||||||
title: "New Notebook",
|
title: "New Notebook",
|
||||||
@@ -235,20 +244,20 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
iconSrc: NewQueryIcon,
|
iconSrc: NewQueryIcon,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null);
|
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, undefined);
|
||||||
},
|
},
|
||||||
title: "New SQL Query",
|
title: "New SQL Query",
|
||||||
description: null,
|
description: undefined,
|
||||||
});
|
});
|
||||||
} else if (userContext.apiType === "Mongo") {
|
} else if (userContext.apiType === "Mongo") {
|
||||||
items.push({
|
items.push({
|
||||||
iconSrc: NewQueryIcon,
|
iconSrc: NewQueryIcon,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null);
|
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, undefined);
|
||||||
},
|
},
|
||||||
title: "New Query",
|
title: "New Query",
|
||||||
description: null,
|
description: undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,8 +265,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
items.push({
|
items.push({
|
||||||
iconSrc: OpenQueryIcon,
|
iconSrc: OpenQueryIcon,
|
||||||
title: "Open Query",
|
title: "Open Query",
|
||||||
description: null,
|
description: undefined,
|
||||||
onClick: () => this.container.openBrowseQueriesPanel(),
|
onClick: () =>
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={this.container} />),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,22 +277,22 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
items.push({
|
items.push({
|
||||||
iconSrc: NewStoredProcedureIcon,
|
iconSrc: NewStoredProcedureIcon,
|
||||||
title: "New Stored Procedure",
|
title: "New Stored Procedure",
|
||||||
description: null,
|
description: undefined,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null);
|
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, undefined);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scale & Settings */
|
/* Scale & Settings */
|
||||||
const isShared = useSelectedNode.getState().findSelectedDatabase()?.isDatabaseShared();
|
const isShared = useDatabases.getState().findSelectedDatabase()?.isDatabaseShared();
|
||||||
|
|
||||||
const label = isShared ? "Settings" : "Scale & Settings";
|
const label = isShared ? "Settings" : "Scale & Settings";
|
||||||
items.push({
|
items.push({
|
||||||
iconSrc: ScaleAndSettingsIcon,
|
iconSrc: ScaleAndSettingsIcon,
|
||||||
title: label,
|
title: label,
|
||||||
description: null,
|
description: undefined,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onSettingsClick();
|
selectedCollection && selectedCollection.onSettingsClick();
|
||||||
@@ -290,8 +302,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
items.push({
|
items.push({
|
||||||
iconSrc: AddDatabaseIcon,
|
iconSrc: AddDatabaseIcon,
|
||||||
title: "New " + getDatabaseName(),
|
title: "New " + getDatabaseName(),
|
||||||
description: null,
|
description: undefined,
|
||||||
onClick: () => this.container.openAddDatabasePane(),
|
onClick: () =>
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={this.container} />),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,19 +357,19 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
private createTipsItems(): SplashScreenItem[] {
|
private createTipsItems(): SplashScreenItem[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
iconSrc: null,
|
iconSrc: undefined,
|
||||||
title: "Data Modeling",
|
title: "Data Modeling",
|
||||||
description: "Learn more about modeling",
|
description: "Learn more about modeling",
|
||||||
onClick: () => window.open(SplashScreen.dataModelingUrl),
|
onClick: () => window.open(SplashScreen.dataModelingUrl),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
iconSrc: null,
|
iconSrc: undefined,
|
||||||
title: "Cost & Throughput Calculation",
|
title: "Cost & Throughput Calculation",
|
||||||
description: "Learn more about cost calculation",
|
description: "Learn more about cost calculation",
|
||||||
onClick: () => window.open(SplashScreen.throughputEstimatorUrl),
|
onClick: () => window.open(SplashScreen.throughputEstimatorUrl),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
iconSrc: null,
|
iconSrc: undefined,
|
||||||
title: "Configure automatic failover",
|
title: "Configure automatic failover",
|
||||||
description: "Learn more about Cosmos DB high-availability",
|
description: "Learn more about Cosmos DB high-availability",
|
||||||
onClick: () => window.open(SplashScreen.failoverUrl),
|
onClick: () => window.open(SplashScreen.failoverUrl),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export var TableType = {
|
export const TableType = {
|
||||||
String: "String",
|
String: "String",
|
||||||
Boolean: "Boolean",
|
Boolean: "Boolean",
|
||||||
Binary: "Binary",
|
Binary: "Binary",
|
||||||
@@ -9,7 +9,7 @@ export var TableType = {
|
|||||||
Int64: "Int64",
|
Int64: "Int64",
|
||||||
};
|
};
|
||||||
|
|
||||||
export var CassandraType = {
|
export const CassandraType = {
|
||||||
Ascii: "Ascii",
|
Ascii: "Ascii",
|
||||||
Bigint: "Bigint",
|
Bigint: "Bigint",
|
||||||
Blob: "Blob",
|
Blob: "Blob",
|
||||||
@@ -27,12 +27,12 @@ export var CassandraType = {
|
|||||||
Tinyint: "Tinyint",
|
Tinyint: "Tinyint",
|
||||||
};
|
};
|
||||||
|
|
||||||
export var ClauseRule = {
|
export const ClauseRule = {
|
||||||
And: "And",
|
And: "And",
|
||||||
Or: "Or",
|
Or: "Or",
|
||||||
};
|
};
|
||||||
|
|
||||||
export var Operator = {
|
export const Operator = {
|
||||||
EqualTo: "==",
|
EqualTo: "==",
|
||||||
GreaterThan: ">",
|
GreaterThan: ">",
|
||||||
GreaterThanOrEqualTo: ">=",
|
GreaterThanOrEqualTo: ">=",
|
||||||
@@ -42,7 +42,7 @@ export var Operator = {
|
|||||||
Equal: "=",
|
Equal: "=",
|
||||||
};
|
};
|
||||||
|
|
||||||
export var ODataOperator = {
|
export const ODataOperator = {
|
||||||
EqualTo: "eq",
|
EqualTo: "eq",
|
||||||
GreaterThan: "gt",
|
GreaterThan: "gt",
|
||||||
GreaterThanOrEqualTo: "ge",
|
GreaterThanOrEqualTo: "ge",
|
||||||
@@ -51,7 +51,7 @@ export var ODataOperator = {
|
|||||||
NotEqualTo: "ne",
|
NotEqualTo: "ne",
|
||||||
};
|
};
|
||||||
|
|
||||||
export var timeOptions = {
|
export const timeOptions = {
|
||||||
lastHour: "Last hour",
|
lastHour: "Last hour",
|
||||||
last24Hours: "Last 24 hours",
|
last24Hours: "Last 24 hours",
|
||||||
last7Days: "Last 7 days",
|
last7Days: "Last 7 days",
|
||||||
@@ -62,7 +62,7 @@ export var timeOptions = {
|
|||||||
custom: "Custom...",
|
custom: "Custom...",
|
||||||
};
|
};
|
||||||
|
|
||||||
export var htmlSelectors = {
|
export const htmlSelectors = {
|
||||||
dataTableSelector: "#storageTable",
|
dataTableSelector: "#storageTable",
|
||||||
dataTableAllRowsSelector: "#storageTable tbody tr",
|
dataTableAllRowsSelector: "#storageTable tbody tr",
|
||||||
dataTableHeadRowSelector: ".dataTable thead tr",
|
dataTableHeadRowSelector: ".dataTable thead tr",
|
||||||
@@ -84,9 +84,9 @@ export var htmlSelectors = {
|
|||||||
selectAllDropdownSelector: "#select-all-dropdown",
|
selectAllDropdownSelector: "#select-all-dropdown",
|
||||||
};
|
};
|
||||||
|
|
||||||
export var defaultHeader = " ";
|
export const defaultHeader = " ";
|
||||||
|
|
||||||
export var EntityKeyNames = {
|
export const EntityKeyNames = {
|
||||||
PartitionKey: "PartitionKey",
|
PartitionKey: "PartitionKey",
|
||||||
RowKey: "RowKey",
|
RowKey: "RowKey",
|
||||||
Timestamp: "Timestamp",
|
Timestamp: "Timestamp",
|
||||||
@@ -94,7 +94,7 @@ export var EntityKeyNames = {
|
|||||||
Etag: "etag",
|
Etag: "etag",
|
||||||
};
|
};
|
||||||
|
|
||||||
export var htmlAttributeNames = {
|
export const htmlAttributeNames = {
|
||||||
dataTableNameAttr: "name_attr",
|
dataTableNameAttr: "name_attr",
|
||||||
dataTableContentTypeAttr: "contentType_attr",
|
dataTableContentTypeAttr: "contentType_attr",
|
||||||
dataTableSnapshotAttr: "snapshot_attr",
|
dataTableSnapshotAttr: "snapshot_attr",
|
||||||
@@ -103,14 +103,14 @@ export var htmlAttributeNames = {
|
|||||||
dataTableHeaderIndex: "data-column-index",
|
dataTableHeaderIndex: "data-column-index",
|
||||||
};
|
};
|
||||||
|
|
||||||
export var cssColors = {
|
export const cssColors = {
|
||||||
commonControlsButtonActive: "#B4C7DC" /* A darker shade of [{common-controls-button-hover-background}] */,
|
commonControlsButtonActive: "#B4C7DC" /* A darker shade of [{common-controls-button-hover-background}] */,
|
||||||
};
|
};
|
||||||
|
|
||||||
export var clauseGroupColors = ["#ffe1ff", "#fffacd", "#f0ffff", "#ffefd5", "#f0fff0"];
|
export const clauseGroupColors = ["#ffe1ff", "#fffacd", "#f0ffff", "#ffefd5", "#f0fff0"];
|
||||||
export var transparentColor = "transparent";
|
export const transparentColor = "transparent";
|
||||||
|
|
||||||
export var keyCodes = {
|
export const keyCodes = {
|
||||||
RightClick: 3,
|
RightClick: 3,
|
||||||
Enter: 13,
|
Enter: 13,
|
||||||
Esc: 27,
|
Esc: 27,
|
||||||
@@ -163,7 +163,7 @@ export var keyCodes = {
|
|||||||
Dash: 189,
|
Dash: 189,
|
||||||
};
|
};
|
||||||
|
|
||||||
export var InputType = {
|
export const InputType = {
|
||||||
Text: "text",
|
Text: "text",
|
||||||
// Chrome doesn't support datetime, instead, datetime-local is supported.
|
// Chrome doesn't support datetime, instead, datetime-local is supported.
|
||||||
DateTime: "datetime-local",
|
DateTime: "datetime-local",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export function getQuotedCqlIdentifier(identifier: string): string {
|
// Added return type optional undefined because passing undefined from test cases.
|
||||||
|
export function getQuotedCqlIdentifier(identifier: string | undefined): string | undefined {
|
||||||
let result = identifier;
|
let result = identifier;
|
||||||
if (!identifier) {
|
if (!identifier) {
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
|
import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
|
|
||||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
import CacheBase from "./CacheBase";
|
|
||||||
import * as CommonConstants from "../../../Common/Constants";
|
import * as CommonConstants from "../../../Common/Constants";
|
||||||
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { NewQueryTablesTab } from "../../Tabs/QueryTablesTab/NewQueryTablesTab";
|
||||||
import * as Constants from "../Constants";
|
import * as Constants from "../Constants";
|
||||||
import * as Entities from "../Entities";
|
import * as Entities from "../Entities";
|
||||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
import CacheBase from "./CacheBase";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
|
||||||
|
|
||||||
// This is the format of the data we will have to pass to Datatable render callback,
|
// This is the format of the data we will have to pass to Datatable render callback,
|
||||||
// and property names are defined by Datatable as well.
|
// and property names are defined by Datatable as well.
|
||||||
@@ -49,7 +49,7 @@ abstract class DataTableViewModel {
|
|||||||
|
|
||||||
private dataTableOperationManager: IDataTableOperation;
|
private dataTableOperationManager: IDataTableOperation;
|
||||||
|
|
||||||
public queryTablesTab: QueryTablesTab;
|
public queryTablesTab: NewQueryTablesTab;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.items([]);
|
this.items([]);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
|||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
||||||
|
import { NewQueryTablesTab } from "../../Tabs/QueryTablesTab/NewQueryTablesTab";
|
||||||
import * as Constants from "../Constants";
|
import * as Constants from "../Constants";
|
||||||
import { getQuotedCqlIdentifier } from "../CqlUtilities";
|
import { getQuotedCqlIdentifier } from "../CqlUtilities";
|
||||||
import * as Entities from "../Entities";
|
import * as Entities from "../Entities";
|
||||||
@@ -112,7 +113,7 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
|||||||
public queryErrorMessage: ko.Observable<string>;
|
public queryErrorMessage: ko.Observable<string>;
|
||||||
public id: string;
|
public id: string;
|
||||||
|
|
||||||
constructor(tableCommands: TableCommands, queryTablesTab: QueryTablesTab) {
|
constructor(tableCommands: TableCommands, queryTablesTab: NewQueryTablesTab) {
|
||||||
super();
|
super();
|
||||||
this.cache = new TableEntityCache();
|
this.cache = new TableEntityCache();
|
||||||
this.queryErrorMessage = ko.observable<string>();
|
this.queryErrorMessage = ko.observable<string>();
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { KeyCodes } from "../../../Common/Constants";
|
|||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { TableQuerySelectPanel } from "../../Panes/Tables/TableQuerySelectPanel/TableQuerySelectPanel";
|
import { TableQuerySelectPanel } from "../../Panes/Tables/TableQuerySelectPanel/TableQuerySelectPanel";
|
||||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
import { NewQueryTablesTab } from "../../Tabs/QueryTablesTab/NewQueryTablesTab";
|
||||||
import { getQuotedCqlIdentifier } from "../CqlUtilities";
|
import { getQuotedCqlIdentifier } from "../CqlUtilities";
|
||||||
import * as DataTableUtilities from "../DataTable/DataTableUtilities";
|
import * as DataTableUtilities from "../DataTable/DataTableUtilities";
|
||||||
import TableEntityListViewModel from "../DataTable/TableEntityListViewModel";
|
import TableEntityListViewModel from "../DataTable/TableEntityListViewModel";
|
||||||
@@ -39,14 +39,14 @@ export default class QueryViewModel {
|
|||||||
|
|
||||||
public columnOptions: ko.ObservableArray<string>;
|
public columnOptions: ko.ObservableArray<string>;
|
||||||
|
|
||||||
public queryTablesTab: QueryTablesTab;
|
public queryTablesTab: NewQueryTablesTab;
|
||||||
public id: string;
|
public id: string;
|
||||||
private _tableEntityListViewModel: TableEntityListViewModel;
|
private _tableEntityListViewModel: TableEntityListViewModel;
|
||||||
|
|
||||||
constructor(queryTablesTab: QueryTablesTab) {
|
constructor(queryTablesTab: NewQueryTablesTab) {
|
||||||
this.queryTablesTab = queryTablesTab;
|
this.queryTablesTab = queryTablesTab;
|
||||||
this.id = `queryViewModel${this.queryTablesTab.tabId}`;
|
this.id = `queryViewModel${this.queryTablesTab.tabId}`;
|
||||||
this._tableEntityListViewModel = queryTablesTab.tableEntityListViewModel();
|
this._tableEntityListViewModel = queryTablesTab.tableEntityListViewModel;
|
||||||
|
|
||||||
this.queryTextIsReadOnly = ko.computed<boolean>(() => {
|
this.queryTextIsReadOnly = ko.computed<boolean>(() => {
|
||||||
return userContext.apiType !== "Cassandra";
|
return userContext.apiType !== "Cassandra";
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import type { TabOptions } from "../../../Contracts/ViewModels";
|
import type { TabOptions } from "../../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import TabsBase from "../TabsBase";
|
import TabsBase from "../TabsBase";
|
||||||
import MongoShellTabComponent, { IMongoShellTabAccessor, IMongoShellTabComponentProps } from "./MongoShellTabComponent";
|
import MongoShellTabComponent, { IMongoShellTabAccessor, IMongoShellTabComponentProps } from "./MongoShellTabComponent";
|
||||||
@@ -33,7 +34,7 @@ export class NewMongoShellTab extends TabsBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): void {
|
public onTabClick(): void {
|
||||||
this.manager?.activateTab(this);
|
useTabs.getState().activateTab(this);
|
||||||
this.iMongoShellTabAccessor.onTabClickEvent();
|
this.iMongoShellTabAccessor.onTabClickEvent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
|||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { NotebookClientV2 } from "../Notebook/NotebookClientV2";
|
import { NotebookClientV2 } from "../Notebook/NotebookClientV2";
|
||||||
|
import { useNotebook } from "../Notebook/useNotebook";
|
||||||
import TabsBase from "./TabsBase";
|
import TabsBase from "./TabsBase";
|
||||||
|
|
||||||
export interface NotebookTabBaseOptions extends ViewModels.TabOptions {
|
export interface NotebookTabBaseOptions extends ViewModels.TabOptions {
|
||||||
@@ -28,7 +29,7 @@ export default class NotebookTabBase extends TabsBase {
|
|||||||
|
|
||||||
if (!NotebookTabBase.clientManager) {
|
if (!NotebookTabBase.clientManager) {
|
||||||
NotebookTabBase.clientManager = new NotebookClientV2({
|
NotebookTabBase.clientManager = new NotebookClientV2({
|
||||||
connectionInfo: this.container.notebookServerInfo(),
|
connectionInfo: useNotebook.getState().notebookServerInfo,
|
||||||
databaseAccountName: userContext?.databaseAccount?.name,
|
databaseAccountName: userContext?.databaseAccount?.name,
|
||||||
defaultExperience: userContext.apiType,
|
defaultExperience: userContext.apiType,
|
||||||
contentProvider: this.container.notebookManager?.notebookContentProvider,
|
contentProvider: this.container.notebookManager?.notebookContentProvider,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import * as CdbActions from "../Notebook/NotebookComponent/actions";
|
|||||||
import { NotebookComponentAdapter } from "../Notebook/NotebookComponent/NotebookComponentAdapter";
|
import { NotebookComponentAdapter } from "../Notebook/NotebookComponent/NotebookComponentAdapter";
|
||||||
import { CdbAppState, SnapshotRequest } from "../Notebook/NotebookComponent/types";
|
import { CdbAppState, SnapshotRequest } from "../Notebook/NotebookComponent/types";
|
||||||
import { NotebookContentItem } from "../Notebook/NotebookContentItem";
|
import { NotebookContentItem } from "../Notebook/NotebookContentItem";
|
||||||
|
import { useNotebook } from "../Notebook/useNotebook";
|
||||||
import NotebookTabBase, { NotebookTabBaseOptions } from "./NotebookTabBase";
|
import NotebookTabBase, { NotebookTabBaseOptions } from "./NotebookTabBase";
|
||||||
|
|
||||||
export interface NotebookTabOptions extends NotebookTabBaseOptions {
|
export interface NotebookTabOptions extends NotebookTabBaseOptions {
|
||||||
@@ -39,10 +40,13 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
|||||||
|
|
||||||
this.container = options.container;
|
this.container = options.container;
|
||||||
this.notebookPath = ko.observable(options.notebookContentItem.path);
|
this.notebookPath = ko.observable(options.notebookContentItem.path);
|
||||||
this.container.notebookServerInfo.subscribe(() => logConsoleInfo("New notebook server info received."));
|
useNotebook.subscribe(
|
||||||
|
() => logConsoleInfo("New notebook server info received."),
|
||||||
|
(state) => state.notebookServerInfo
|
||||||
|
);
|
||||||
this.notebookComponentAdapter = new NotebookComponentAdapter({
|
this.notebookComponentAdapter = new NotebookComponentAdapter({
|
||||||
contentItem: options.notebookContentItem,
|
contentItem: options.notebookContentItem,
|
||||||
notebooksBasePath: this.container.getNotebookBasePath(),
|
notebooksBasePath: useNotebook.getState().notebookBasePath,
|
||||||
notebookClient: NotebookTabBase.clientManager,
|
notebookClient: NotebookTabBase.clientManager,
|
||||||
onUpdateKernelInfo: this.onKernelUpdate,
|
onUpdateKernelInfo: this.onKernelUpdate,
|
||||||
});
|
});
|
||||||
@@ -359,8 +363,8 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private async configureServiceEndpoints(kernelName: string) {
|
private async configureServiceEndpoints(kernelName: string) {
|
||||||
const notebookConnectionInfo = this.container && this.container.notebookServerInfo();
|
const notebookConnectionInfo = useNotebook.getState().notebookServerInfo;
|
||||||
const sparkClusterConnectionInfo = this.container && this.container.sparkClusterConnectionInfo();
|
const sparkClusterConnectionInfo = useNotebook.getState().sparkClusterConnectionInfo;
|
||||||
await NotebookConfigurationUtils.configureServiceEndpoints(
|
await NotebookConfigurationUtils.configureServiceEndpoints(
|
||||||
this.notebookPath(),
|
this.notebookPath(),
|
||||||
notebookConnectionInfo,
|
notebookConnectionInfo,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import type { QueryTabOptions } from "../../../Contracts/ViewModels";
|
import type { QueryTabOptions } from "../../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { IQueryTabComponentProps, ITabAccessor } from "../../Tabs/QueryTab/QueryTabComponent";
|
import { IQueryTabComponentProps, ITabAccessor } from "../../Tabs/QueryTab/QueryTabComponent";
|
||||||
import TabsBase from "../TabsBase";
|
import TabsBase from "../TabsBase";
|
||||||
@@ -40,12 +41,12 @@ export class NewQueryTab extends TabsBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): void {
|
public onTabClick(): void {
|
||||||
this.manager?.activateTab(this);
|
useTabs.getState().activateTab(this);
|
||||||
this.iTabAccessor.onTabClickEvent();
|
this.iTabAccessor.onTabClickEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onCloseTabButtonClick(): void {
|
public onCloseTabButtonClick(): void {
|
||||||
this.manager?.closeTab(this);
|
useTabs.getState().closeTab(this);
|
||||||
if (this.iTabAccessor) {
|
if (this.iTabAccessor) {
|
||||||
this.iTabAccessor.onCloseClickEvent(true);
|
this.iTabAccessor.onCloseClickEvent(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,14 +22,16 @@ import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
|||||||
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 { useNotificationConsole } from "../../../hooks/useNotificationConsole";
|
import { useNotificationConsole } from "../../../hooks/useNotificationConsole";
|
||||||
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import * as QueryUtils from "../../../Utils/QueryUtils";
|
import * as QueryUtils from "../../../Utils/QueryUtils";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { EditorReact } from "../../Controls/Editor/EditorReact";
|
import { EditorReact } from "../../Controls/Editor/EditorReact";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
|
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
|
import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane";
|
||||||
|
import { SaveQueryPane } from "../../Panes/SaveQueryPane/SaveQueryPane";
|
||||||
import TabsBase from "../TabsBase";
|
import TabsBase from "../TabsBase";
|
||||||
import { TabsManager } from "../TabsManager";
|
|
||||||
import "./QueryTabComponent.less";
|
import "./QueryTabComponent.less";
|
||||||
|
|
||||||
enum ToggleState {
|
enum ToggleState {
|
||||||
@@ -65,7 +67,6 @@ export interface IQueryTabComponentProps {
|
|||||||
partitionKey: DataModels.PartitionKey;
|
partitionKey: DataModels.PartitionKey;
|
||||||
container: Explorer;
|
container: Explorer;
|
||||||
activeTab?: TabsBase;
|
activeTab?: TabsBase;
|
||||||
tabManager?: TabsManager;
|
|
||||||
onTabAccessor: (instance: ITabAccessor) => void;
|
onTabAccessor: (instance: ITabAccessor) => void;
|
||||||
isPreferredApiMongoDB?: boolean;
|
isPreferredApiMongoDB?: boolean;
|
||||||
monacoEditorSetting?: string;
|
monacoEditorSetting?: string;
|
||||||
@@ -391,13 +392,13 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
};
|
};
|
||||||
|
|
||||||
public onSaveQueryClick = (): void => {
|
public onSaveQueryClick = (): void => {
|
||||||
this.props.collection && this.props.collection.container && this.props.collection.container.openSaveQueryPanel();
|
useSidePanel.getState().openSidePanel("Save Query", <SaveQueryPane explorer={this.props.collection.container} />);
|
||||||
};
|
};
|
||||||
|
|
||||||
public onSavedQueriesClick = (): void => {
|
public onSavedQueriesClick = (): void => {
|
||||||
this.props.collection &&
|
useSidePanel
|
||||||
this.props.collection.container &&
|
.getState()
|
||||||
this.props.collection.container.openBrowseQueriesPanel();
|
.openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={this.props.collection.container} />);
|
||||||
};
|
};
|
||||||
|
|
||||||
public async onFetchNextPageClick(): Promise<void> {
|
public async onFetchNextPageClick(): Promise<void> {
|
||||||
|
|||||||
@@ -137,13 +137,14 @@ export default class QueryTablesTab extends TabsBase {
|
|||||||
useSidePanel
|
useSidePanel
|
||||||
.getState()
|
.getState()
|
||||||
.openSidePanel(
|
.openSidePanel(
|
||||||
"Add Table Entity",
|
"Add Table Row",
|
||||||
<AddTableEntityPanel
|
<AddTableEntityPanel
|
||||||
tableDataClient={this.tableDataClient}
|
tableDataClient={this.tableDataClient}
|
||||||
queryTablesTab={this}
|
queryTablesTab={this}
|
||||||
tableEntityListViewModel={this.tableEntityListViewModel()}
|
tableEntityListViewModel={this.tableEntityListViewModel()}
|
||||||
cassandraApiClient={new CassandraAPIDataClient()}
|
cassandraApiClient={new CassandraAPIDataClient()}
|
||||||
/>
|
/>,
|
||||||
|
"700px"
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -157,7 +158,8 @@ export default class QueryTablesTab extends TabsBase {
|
|||||||
queryTablesTab={this}
|
queryTablesTab={this}
|
||||||
tableEntityListViewModel={this.tableEntityListViewModel()}
|
tableEntityListViewModel={this.tableEntityListViewModel()}
|
||||||
cassandraApiClient={new CassandraAPIDataClient()}
|
cassandraApiClient={new CassandraAPIDataClient()}
|
||||||
/>
|
/>,
|
||||||
|
"700px"
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
52
src/Explorer/Tabs/QueryTablesTab/NewQueryTablesTab.tsx
Normal file
52
src/Explorer/Tabs/QueryTablesTab/NewQueryTablesTab.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import React from "react";
|
||||||
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
|
import type { TabOptions } from "../../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
import TableCommands from "../../Tables/DataTable/TableCommands";
|
||||||
|
import TableEntityListViewModel from "../../Tables/DataTable/TableEntityListViewModel";
|
||||||
|
import TabsBase from "../TabsBase";
|
||||||
|
import {
|
||||||
|
IQueryTablesTabAccessor,
|
||||||
|
IQueryTablesTabComponentProps,
|
||||||
|
QueryTablesTabComponent,
|
||||||
|
} from "./QueryTablesTabComponent";
|
||||||
|
|
||||||
|
export interface IQueryTablesTabProps {
|
||||||
|
container: Explorer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NewQueryTablesTab extends TabsBase {
|
||||||
|
public queryText: string;
|
||||||
|
public currentQuery: string;
|
||||||
|
public partitionKey: DataModels.PartitionKey;
|
||||||
|
public iQueryTablesTabComponentProps: IQueryTablesTabComponentProps;
|
||||||
|
public tableEntityListViewModel: TableEntityListViewModel;
|
||||||
|
public tableCommands: TableCommands;
|
||||||
|
public iQueryTablesTabAccessor: IQueryTablesTabAccessor;
|
||||||
|
|
||||||
|
constructor(options: TabOptions, private props: IQueryTablesTabProps) {
|
||||||
|
super(options);
|
||||||
|
this.tableCommands = new TableCommands(props.container);
|
||||||
|
this.tableEntityListViewModel = new TableEntityListViewModel(this.tableCommands, this);
|
||||||
|
this.iQueryTablesTabComponentProps = {
|
||||||
|
collection: this.collection,
|
||||||
|
tabId: this.tabId,
|
||||||
|
tabsBaseInstance: this,
|
||||||
|
queryTablesTab: this,
|
||||||
|
container: this.props.container,
|
||||||
|
onQueryTablesTabAccessor: (instance: IQueryTablesTabAccessor) => {
|
||||||
|
this.iQueryTablesTabAccessor = instance;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
return <QueryTablesTabComponent {...this.iQueryTablesTabComponentProps} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
public onTabClick(): void {
|
||||||
|
useTabs.getState().activateTab(this);
|
||||||
|
this.iQueryTablesTabAccessor.onTabClickEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
619
src/Explorer/Tabs/QueryTablesTab/QueryTablesTabComponent.tsx
Normal file
619
src/Explorer/Tabs/QueryTablesTab/QueryTablesTabComponent.tsx
Normal file
@@ -0,0 +1,619 @@
|
|||||||
|
import React from "react";
|
||||||
|
import AddEntityIcon from "../../../../images/AddEntity.svg";
|
||||||
|
import DeleteEntitiesIcon from "../../../../images/DeleteEntities.svg";
|
||||||
|
import EditEntityIcon from "../../../../images/Edit-entity.svg";
|
||||||
|
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
|
||||||
|
import QueryBuilderIcon from "../../../../images/Query-Builder.svg";
|
||||||
|
import QueryTextIcon from "../../../../images/Query-Text.svg";
|
||||||
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
|
import { userContext } from "../../../UserContext";
|
||||||
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
import { AddTableEntityPanel } from "../../Panes/Tables/AddTableEntityPanel";
|
||||||
|
import { EditTableEntityPanel } from "../../Panes/Tables/EditTableEntityPanel";
|
||||||
|
import TableCommands from "../../Tables/DataTable/TableCommands";
|
||||||
|
import TableEntityListViewModel from "../../Tables/DataTable/TableEntityListViewModel";
|
||||||
|
import QueryViewModel from "../../Tables/QueryBuilder/QueryViewModel";
|
||||||
|
import { CassandraAPIDataClient, TableDataClient } from "../../Tables/TableDataClient";
|
||||||
|
import TabsBase from "../TabsBase";
|
||||||
|
import { NewQueryTablesTab } from "./NewQueryTablesTab";
|
||||||
|
|
||||||
|
export interface IQueryTablesTabAccessor {
|
||||||
|
onTabClickEvent: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Button {
|
||||||
|
visible: boolean;
|
||||||
|
enabled: boolean;
|
||||||
|
isSelected?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IQueryTablesTabComponentStates {
|
||||||
|
queryViewModel: QueryViewModel;
|
||||||
|
queryText: string;
|
||||||
|
selectedQueryText: string;
|
||||||
|
executeQueryButton: Button;
|
||||||
|
queryBuilderButton: Button;
|
||||||
|
queryTextButton: Button;
|
||||||
|
addEntityButton: Button;
|
||||||
|
editEntityButton: Button;
|
||||||
|
deleteEntityButton: Button;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IQueryTablesTabComponentProps {
|
||||||
|
collection: ViewModels.CollectionBase;
|
||||||
|
tabsBaseInstance: TabsBase;
|
||||||
|
tabId: string;
|
||||||
|
queryTablesTab: NewQueryTablesTab;
|
||||||
|
container: Explorer;
|
||||||
|
onQueryTablesTabAccessor: (instance: IQueryTablesTabAccessor) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class QueryTablesTabComponent extends React.Component<
|
||||||
|
IQueryTablesTabComponentProps,
|
||||||
|
IQueryTablesTabComponentStates
|
||||||
|
> {
|
||||||
|
public collection: ViewModels.Collection;
|
||||||
|
public tableEntityListViewModel: TableEntityListViewModel;
|
||||||
|
public tableCommands: TableCommands;
|
||||||
|
public tableDataClient: TableDataClient;
|
||||||
|
public container: Explorer;
|
||||||
|
public queryViewModel: QueryViewModel;
|
||||||
|
|
||||||
|
constructor(props: IQueryTablesTabComponentProps) {
|
||||||
|
super(props);
|
||||||
|
this.container = props.collection && props.collection.container;
|
||||||
|
this.tableCommands = new TableCommands(this.container);
|
||||||
|
this.tableDataClient = this.container.tableDataClient;
|
||||||
|
this.tableEntityListViewModel = new TableEntityListViewModel(this.tableCommands, props.queryTablesTab);
|
||||||
|
this.tableEntityListViewModel.queryTablesTab = props.queryTablesTab;
|
||||||
|
this.queryViewModel = new QueryViewModel(props.queryTablesTab);
|
||||||
|
this.state = {
|
||||||
|
queryViewModel: this.queryViewModel,
|
||||||
|
queryText: "PartitionKey eq 'partitionKey1'",
|
||||||
|
selectedQueryText: "",
|
||||||
|
executeQueryButton: {
|
||||||
|
enabled: true,
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
queryBuilderButton: {
|
||||||
|
enabled: true,
|
||||||
|
visible: true,
|
||||||
|
isSelected: this.queryViewModel ? this.queryViewModel.isHelperActive() : false,
|
||||||
|
},
|
||||||
|
queryTextButton: {
|
||||||
|
enabled: true,
|
||||||
|
visible: true,
|
||||||
|
isSelected: this.queryViewModel ? this.queryViewModel.isEditorActive() : false,
|
||||||
|
},
|
||||||
|
addEntityButton: {
|
||||||
|
enabled: true,
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
editEntityButton: {
|
||||||
|
enabled: this.tableCommands.isEnabled(
|
||||||
|
TableCommands.editEntityCommand,
|
||||||
|
this.tableEntityListViewModel.selected()
|
||||||
|
),
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
deleteEntityButton: {
|
||||||
|
enabled: this.tableCommands.isEnabled(
|
||||||
|
TableCommands.deleteEntitiesCommand,
|
||||||
|
this.tableEntityListViewModel.selected()
|
||||||
|
),
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (this.tableEntityListViewModel.items().length > 0 && userContext.apiType === "Tables") {
|
||||||
|
const sampleQuerySubscription = this.state.queryViewModel.queryBuilderViewModel().setExample();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public onExecuteQueryClick(): void {
|
||||||
|
this.state.queryViewModel.runQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
public onQueryBuilderClick(): void {
|
||||||
|
this.state.queryViewModel.selectHelper();
|
||||||
|
}
|
||||||
|
|
||||||
|
public onQueryTextClick(): void {
|
||||||
|
this.state.queryViewModel.selectEditor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public onAddEntityClick(): void {
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel(
|
||||||
|
"Add Table Row",
|
||||||
|
<AddTableEntityPanel
|
||||||
|
tableDataClient={this.tableDataClient}
|
||||||
|
queryTablesTab={this.props.queryTablesTab}
|
||||||
|
tableEntityListViewModel={this.tableEntityListViewModel}
|
||||||
|
cassandraApiClient={new CassandraAPIDataClient()}
|
||||||
|
/>,
|
||||||
|
"700px"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onEditEntityClick(): void {
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel(
|
||||||
|
"Edit Table Entity",
|
||||||
|
<EditTableEntityPanel
|
||||||
|
tableDataClient={this.tableDataClient}
|
||||||
|
queryTablesTab={this.props.queryTablesTab}
|
||||||
|
tableEntityListViewModel={this.tableEntityListViewModel}
|
||||||
|
cassandraApiClient={new CassandraAPIDataClient()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onDeleteEntityClick(): void {
|
||||||
|
this.tableCommands.deleteEntitiesCommand(this.tableEntityListViewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onActivate(): void {
|
||||||
|
const columns =
|
||||||
|
!!this.tableEntityListViewModel &&
|
||||||
|
!!this.tableEntityListViewModel.table &&
|
||||||
|
this.tableEntityListViewModel.table.columns;
|
||||||
|
if (!!columns) {
|
||||||
|
columns.adjust();
|
||||||
|
$(window).resize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTabsButtons(): CommandButtonComponentProps[] {
|
||||||
|
const buttons: CommandButtonComponentProps[] = [];
|
||||||
|
if (this.state.queryBuilderButton.visible) {
|
||||||
|
const label = userContext.apiType === "Cassandra" ? "CQL Query Builder" : "Query Builder";
|
||||||
|
buttons.push({
|
||||||
|
iconSrc: QueryBuilderIcon,
|
||||||
|
iconAlt: label,
|
||||||
|
onCommandClick: this.onQueryBuilderClick,
|
||||||
|
commandButtonLabel: label,
|
||||||
|
ariaLabel: label,
|
||||||
|
hasPopup: false,
|
||||||
|
disabled: !this.state.queryBuilderButton.enabled,
|
||||||
|
isSelected: this.state.queryBuilderButton.isSelected,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.queryTextButton.visible) {
|
||||||
|
const label = userContext.apiType === "Cassandra" ? "CQL Query Text" : "Query Text";
|
||||||
|
buttons.push({
|
||||||
|
iconSrc: QueryTextIcon,
|
||||||
|
iconAlt: label,
|
||||||
|
onCommandClick: this.onQueryTextClick,
|
||||||
|
commandButtonLabel: label,
|
||||||
|
ariaLabel: label,
|
||||||
|
hasPopup: false,
|
||||||
|
disabled: !this.state.queryTextButton.enabled,
|
||||||
|
isSelected: this.state.queryTextButton.isSelected,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.executeQueryButton.visible) {
|
||||||
|
const label = "Run Query";
|
||||||
|
buttons.push({
|
||||||
|
iconSrc: ExecuteQueryIcon,
|
||||||
|
iconAlt: label,
|
||||||
|
onCommandClick: this.onExecuteQueryClick,
|
||||||
|
commandButtonLabel: label,
|
||||||
|
ariaLabel: label,
|
||||||
|
hasPopup: false,
|
||||||
|
disabled: !this.state.executeQueryButton.enabled,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.addEntityButton.visible) {
|
||||||
|
const label = userContext.apiType === "Cassandra" ? "Add Row" : "Add Entity";
|
||||||
|
buttons.push({
|
||||||
|
iconSrc: AddEntityIcon,
|
||||||
|
iconAlt: label,
|
||||||
|
onCommandClick: this.onAddEntityClick,
|
||||||
|
commandButtonLabel: label,
|
||||||
|
ariaLabel: label,
|
||||||
|
hasPopup: true,
|
||||||
|
disabled: !this.state.addEntityButton.enabled,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.editEntityButton.visible) {
|
||||||
|
const label = userContext.apiType === "Cassandra" ? "Edit Row" : "Edit Entity";
|
||||||
|
buttons.push({
|
||||||
|
iconSrc: EditEntityIcon,
|
||||||
|
iconAlt: label,
|
||||||
|
onCommandClick: this.onEditEntityClick,
|
||||||
|
commandButtonLabel: label,
|
||||||
|
ariaLabel: label,
|
||||||
|
hasPopup: true,
|
||||||
|
disabled: !this.state.editEntityButton.enabled,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.deleteEntityButton.visible) {
|
||||||
|
const label = userContext.apiType === "Cassandra" ? "Delete Rows" : "Delete Entities";
|
||||||
|
buttons.push({
|
||||||
|
iconSrc: DeleteEntitiesIcon,
|
||||||
|
iconAlt: label,
|
||||||
|
onCommandClick: this.onDeleteEntityClick,
|
||||||
|
commandButtonLabel: label,
|
||||||
|
ariaLabel: label,
|
||||||
|
hasPopup: true,
|
||||||
|
disabled: !this.state.deleteEntityButton.enabled,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
const { tabId } = this.props;
|
||||||
|
const { queryViewModel, queryText } = this.state;
|
||||||
|
const {
|
||||||
|
isEditorActive,
|
||||||
|
isHelperActive,
|
||||||
|
hasQueryError,
|
||||||
|
queryErrorMessage,
|
||||||
|
queryBuilderViewModel,
|
||||||
|
toggleAdvancedOptions,
|
||||||
|
ontoggleAdvancedOptionsKeyDown,
|
||||||
|
} = queryViewModel;
|
||||||
|
const {
|
||||||
|
canGroupClauses,
|
||||||
|
andLabel,
|
||||||
|
actionLabel,
|
||||||
|
groupClauses,
|
||||||
|
groupSelectedClauses,
|
||||||
|
fieldLabel,
|
||||||
|
dataTypeLabel,
|
||||||
|
operatorLabel,
|
||||||
|
valueLabel,
|
||||||
|
clauseArray,
|
||||||
|
columnOptions,
|
||||||
|
clauseRules,
|
||||||
|
operators,
|
||||||
|
insertNewFilterLine,
|
||||||
|
onAddClauseKeyDown,
|
||||||
|
addClauseIndex,
|
||||||
|
removeThisFilterLine,
|
||||||
|
onDeleteClauseKeyDown,
|
||||||
|
deleteClause,
|
||||||
|
getClauseGroupViewModels,
|
||||||
|
updateColumnOptions,
|
||||||
|
edmTypes,
|
||||||
|
addNewClause,
|
||||||
|
addNewClauseLine,
|
||||||
|
} = queryBuilderViewModel();
|
||||||
|
console.log("queryBuilderViewModel()", queryBuilderViewModel());
|
||||||
|
console.log("clauseArray()", clauseArray());
|
||||||
|
console.log("clauseRules()", clauseRules());
|
||||||
|
console.log("columnOptions()", columnOptions());
|
||||||
|
console.log("operators()", operators());
|
||||||
|
return (
|
||||||
|
<div className="tab-pane tableContainer" id={tabId} role="tabpanel">
|
||||||
|
<div className="query-builder" id={queryViewModel.id}>
|
||||||
|
<div className="error-bar">
|
||||||
|
{hasQueryError && (
|
||||||
|
<div className="error-message" aria-label="Error Message">
|
||||||
|
<span>
|
||||||
|
<img className="entity-error-Img" src="/error_red.svg" />
|
||||||
|
</span>
|
||||||
|
<span className="error-text" role="alert">
|
||||||
|
{queryErrorMessage}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isEditorActive() && (
|
||||||
|
<div className="query-editor-panel">
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
className="query-editor-text"
|
||||||
|
data-bind="
|
||||||
|
css: { 'query-editor-text-invalid': hasQueryError },
|
||||||
|
readOnly: true"
|
||||||
|
name="query-editor"
|
||||||
|
rows={5}
|
||||||
|
cols={100}
|
||||||
|
>
|
||||||
|
{queryText}
|
||||||
|
</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{isHelperActive && (
|
||||||
|
<div style={{ paddingLeft: "13px" }}>
|
||||||
|
<div className="clause-table" data-bind="with: queryBuilderViewModel ">
|
||||||
|
<div className="scroll-box scrollable" id="scroll">
|
||||||
|
<table className="clause-table">
|
||||||
|
<thead>
|
||||||
|
<tr className="clause-table-row">
|
||||||
|
<th className="clause-table-cell header-background action-header">
|
||||||
|
<span>{actionLabel}</span>
|
||||||
|
</th>
|
||||||
|
<th className="clause-table-cell header-background group-control-header">
|
||||||
|
{canGroupClauses && (
|
||||||
|
<button type="button" onClick={groupClauses} title={groupSelectedClauses}>
|
||||||
|
<img className="and-or-svg" src="/And-Or.svg" alt="Group selected clauses" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</th>
|
||||||
|
<th className="clause-table-cell header-background"></th>
|
||||||
|
<th className="clause-table-cell header-background and-or-header">
|
||||||
|
<span>{andLabel}</span>
|
||||||
|
</th>
|
||||||
|
<th className="clause-table-cell header-background field-header">
|
||||||
|
<span>{fieldLabel}</span>
|
||||||
|
</th>
|
||||||
|
<th className="clause-table-cell header-background type-header">
|
||||||
|
<span>{dataTypeLabel}</span>
|
||||||
|
</th>
|
||||||
|
<th className="clause-table-cell header-background operator-header">
|
||||||
|
<span>{operatorLabel}</span>
|
||||||
|
</th>
|
||||||
|
<th className="clause-table-cell header-background value-header">
|
||||||
|
<span>{valueLabel}</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{clauseArray().map((data, index) => (
|
||||||
|
<tr className="clause-table-row">
|
||||||
|
<td className="clause-table-cell action-column">
|
||||||
|
<span
|
||||||
|
className="entity-Add-Cancel"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={addClauseIndex.bind(data, index)}
|
||||||
|
onKeyDown={onAddClauseKeyDown.bind(data, index)}
|
||||||
|
title={insertNewFilterLine}
|
||||||
|
>
|
||||||
|
<img className="querybuilder-addpropertyImg" src="/Add-property.svg" alt="Add clause" />
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="entity-Add-Cancel"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
data-bind="hasFocus: isDeleteButtonFocused"
|
||||||
|
onClick={deleteClause.bind(data, index)}
|
||||||
|
onKeyDown={onDeleteClauseKeyDown.bind(data, index)}
|
||||||
|
title={removeThisFilterLine}
|
||||||
|
>
|
||||||
|
<img className="querybuilder-cancelImg" src="/Entity_cancel.svg" alt="Delete clause" />
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
{/* <td className="clause-table-cell group-control-column">
|
||||||
|
<input type="checkbox" aria-label="And/Or" checked={checkedForGrouping} />
|
||||||
|
</td> */}
|
||||||
|
{/* <td>
|
||||||
|
<table className="group-indicator-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
{getClauseGroupViewModels(data).map((gi, index) => (
|
||||||
|
<td
|
||||||
|
className="group-indicator-column"
|
||||||
|
style={{
|
||||||
|
backgroundColor: gi.backgroundColor,
|
||||||
|
borderTop: gi.showTopBorder.peek() ? "solid thin #CCCCCC" : "none",
|
||||||
|
borderLeft: gi.showLeftBorder.peek() ? "solid thin #CCCCCC" : "none",
|
||||||
|
borderBottom: gi.showBottomBorder.peek()
|
||||||
|
? "solid thin #CCCCCC"
|
||||||
|
: gi.borderBackgroundColor,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{gi.canUngroup && (
|
||||||
|
<button type="button" onClick={} title={ungroupClausesLabel}>
|
||||||
|
<img src="/QueryBuilder/UngroupClause_16x.png" alt="Ungroup clauses" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
<td className="clause-table-cell and-or-column">
|
||||||
|
{canAnd && (
|
||||||
|
<select
|
||||||
|
className="clause-table-field and-or-column"
|
||||||
|
data-bind=" value: and_or, "
|
||||||
|
autoFocus={isAndOrFocused}
|
||||||
|
aria-label=" and_or "
|
||||||
|
>
|
||||||
|
{clauseRules().map((data, index) => (
|
||||||
|
<option value={data}>{data}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="clause-table-cell field-column" onClick={updateColumnOptions}>
|
||||||
|
<select
|
||||||
|
aria-label={field}
|
||||||
|
className="clause-table-field field-column"
|
||||||
|
data-bind=" value: field"
|
||||||
|
>
|
||||||
|
{columnOptions().map((data, index) => (
|
||||||
|
<option value={data}>{data}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td className="clause-table-cell type-column">
|
||||||
|
{isTypeEditable && (
|
||||||
|
<select
|
||||||
|
className="clause-table-field type-column"
|
||||||
|
aria-label={type}
|
||||||
|
value={type}
|
||||||
|
data-bind="css: {'query-builder-isDisabled': !isTypeEditable()}"
|
||||||
|
>
|
||||||
|
{edmTypes.map((data) => (
|
||||||
|
<option>{parent.edmTypes} </option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="clause-table-cell operator-column">
|
||||||
|
{isOperaterEditable && (
|
||||||
|
<select
|
||||||
|
className="clause-table-field operator-column"
|
||||||
|
aria-label={operator}
|
||||||
|
data-bind="
|
||||||
|
value: operator,
|
||||||
|
css: {'query-builder-isDisabled': !isOperaterEditable()}"
|
||||||
|
>
|
||||||
|
{operators().map((data, index) => (
|
||||||
|
<option key={index}>{data}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="clause-table-cell value-column">
|
||||||
|
{isValue && (
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="clause-table-field value-column"
|
||||||
|
type="search"
|
||||||
|
data-bind="textInput: value"
|
||||||
|
aria-label={valueLabel}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isTimestamp && (
|
||||||
|
<select
|
||||||
|
className="clause-table-field time-column"
|
||||||
|
data-bind="options: $parent.timeOptions, value: timeValue"
|
||||||
|
></select>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isCustomLastTimestamp && (
|
||||||
|
<input
|
||||||
|
className="clause-table-field time-column"
|
||||||
|
value={customTimeValue}
|
||||||
|
onClick={customTimestampDialog}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isCustomRangeTimestamp && (
|
||||||
|
<input
|
||||||
|
className="clause-table-field time-column"
|
||||||
|
type="datetime-local"
|
||||||
|
step="1"
|
||||||
|
value={customTimeValue}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
*/}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="addClause"
|
||||||
|
role="button"
|
||||||
|
onClick={addNewClause}
|
||||||
|
data-bind="event: { keydown: onAddNewClauseKeyDown }"
|
||||||
|
title={addNewClauseLine}
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="addClause-heading">
|
||||||
|
<span className="clause-table addClause-title">
|
||||||
|
<img
|
||||||
|
className="addclauseProperty-Img"
|
||||||
|
style={{ marginBottom: "5px" }}
|
||||||
|
src="/Add-property.svg"
|
||||||
|
alt="Add new clause"
|
||||||
|
/>
|
||||||
|
<span style={{ marginLeft: "5px" }}>{addNewClauseLine}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="advanced-options-panel">
|
||||||
|
{/* <div className="advanced-heading">
|
||||||
|
<span
|
||||||
|
className="advanced-title"
|
||||||
|
role="button"
|
||||||
|
onClick={toggleAdvancedOptions}
|
||||||
|
onKeyDown={ontoggleAdvancedOptionsKeyDown}
|
||||||
|
aria-expanded={isExpanded() ? "true" : "false"}
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="themed-images"
|
||||||
|
type="text/html"
|
||||||
|
id="ExpandChevronRight"
|
||||||
|
data-bind="hasFocus: focusExpandIcon"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className="imgiconwidth expand-triangle expand-triangle-right"
|
||||||
|
src="/Triangle-right.svg"
|
||||||
|
alt="toggle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="themed-images" type="text/html" id="ExpandChevronDown">
|
||||||
|
<img className="imgiconwidth expand-triangle" src="/Triangle-down.svg" alt="toggle" />
|
||||||
|
</div>
|
||||||
|
<span>Advanced Options</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{isExpanded && (
|
||||||
|
<div className="advanced-options">
|
||||||
|
<div className="top">
|
||||||
|
<span>Show top results:</span>
|
||||||
|
<input
|
||||||
|
className="top-input"
|
||||||
|
type="number"
|
||||||
|
autoFocus={focusTopResult}
|
||||||
|
value={topValue}
|
||||||
|
title={topValueLimitMessage}
|
||||||
|
role="textbox"
|
||||||
|
aria-label="Show top results"
|
||||||
|
/>
|
||||||
|
{isExceedingLimit && (
|
||||||
|
<div role="alert" aria-atomic="true" className="inline-div">
|
||||||
|
<img className="advanced-options-icon" src="/QueryBuilder/StatusWarning_16x.png" />
|
||||||
|
<span>{topValueLimitMessage}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="select">
|
||||||
|
<span> Select fields for query: </span>
|
||||||
|
{isSelected && (
|
||||||
|
<div>
|
||||||
|
<img className="advanced-options-icon" src="/QueryBuilder/QueryInformation_16x.png" />
|
||||||
|
<span className="select-options-text">{selectMessage}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<a
|
||||||
|
className="select-options-link"
|
||||||
|
onKeyDown={onselectQueryOptionsKeyDown}
|
||||||
|
onClick={selectQueryOptions}
|
||||||
|
tabIndex={0}
|
||||||
|
role="link"
|
||||||
|
>
|
||||||
|
<span>Choose Columns... </span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)} */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="tablesQueryTab tableContainer" id={this.tableEntityListViewModel.id}>
|
||||||
|
<table
|
||||||
|
id="storageTable"
|
||||||
|
className="storage azure-table show-gridlines"
|
||||||
|
tabIndex={0}
|
||||||
|
data-bind="tableSource: items, tableSelection: selected"
|
||||||
|
></table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import { ExecuteSprocResult } from "../../../Common/dataAccess/executeStoredProcedure";
|
import { ExecuteSprocResult } from "../../../Common/dataAccess/executeStoredProcedure";
|
||||||
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 { useTabs } from "../../../hooks/useTabs";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import StoredProcedure from "../../Tree/StoredProcedure";
|
import StoredProcedure from "../../Tree/StoredProcedure";
|
||||||
import ScriptTabBase from "../ScriptTabBase";
|
import ScriptTabBase from "../ScriptTabBase";
|
||||||
@@ -51,12 +52,12 @@ export class NewStoredProcedureTab extends ScriptTabBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): void {
|
public onTabClick(): void {
|
||||||
this.manager?.activateTab(this);
|
useTabs.getState().activateTab(this);
|
||||||
this.iStoreProcAccessor.onTabClickEvent();
|
this.iStoreProcAccessor.onTabClickEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onCloseTabButtonClick(): void {
|
public onCloseTabButtonClick(): void {
|
||||||
this.manager?.closeTab(this);
|
useTabs.getState().closeTab(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onExecuteSprocsResult(result: ExecuteSprocResult): void {
|
public onExecuteSprocsResult(result: ExecuteSprocResult): void {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { ExecuteSprocResult } from "../../../Common/dataAccess/executeStoredProc
|
|||||||
import { updateStoredProcedure } from "../../../Common/dataAccess/updateStoredProcedure";
|
import { updateStoredProcedure } from "../../../Common/dataAccess/updateStoredProcedure";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import { useNotificationConsole } from "../../../hooks/useNotificationConsole";
|
import { useNotificationConsole } from "../../../hooks/useNotificationConsole";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { EditorReact } from "../../Controls/Editor/EditorReact";
|
import { EditorReact } from "../../Controls/Editor/EditorReact";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
@@ -144,7 +145,7 @@ export default class StoredProcedureTabComponent extends React.Component<
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): void {
|
public onTabClick(): void {
|
||||||
if (this.props.container.tabsManager.openedTabs().length > 0) {
|
if (useTabs.getState().openedTabs.length > 0) {
|
||||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -396,10 +397,8 @@ export default class StoredProcedureTabComponent extends React.Component<
|
|||||||
editorModel && editorModel.setValue(createdResource.body as string);
|
editorModel && editorModel.setValue(createdResource.body as string);
|
||||||
this.props.scriptTabBaseInstance.editorContent.setBaseline(createdResource.body as string);
|
this.props.scriptTabBaseInstance.editorContent.setBaseline(createdResource.body as string);
|
||||||
this.node = this.collection.createStoredProcedureNode(createdResource);
|
this.node = this.collection.createStoredProcedureNode(createdResource);
|
||||||
this.props.container.tabsManager.openedTabs()[
|
this.props.scriptTabBaseInstance.node = this.node;
|
||||||
this.props.container.tabsManager.openedTabs().length - 1
|
useTabs.getState().updateTab(this.props.scriptTabBaseInstance);
|
||||||
].node = this.node;
|
|
||||||
|
|
||||||
this.props.scriptTabBaseInstance.editorState(ViewModels.ScriptEditorState.exisitingNoEdits);
|
this.props.scriptTabBaseInstance.editorState(ViewModels.ScriptEditorState.exisitingNoEdits);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|||||||
@@ -3,28 +3,32 @@ import React, { useEffect, useRef, useState } from "react";
|
|||||||
import loadingIcon from "../../../images/circular_loader_black_16x16.gif";
|
import loadingIcon from "../../../images/circular_loader_black_16x16.gif";
|
||||||
import errorIcon from "../../../images/close-black.svg";
|
import errorIcon from "../../../images/close-black.svg";
|
||||||
import { useObservable } from "../../hooks/useObservable";
|
import { useObservable } from "../../hooks/useObservable";
|
||||||
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
import TabsBase from "./TabsBase";
|
import TabsBase from "./TabsBase";
|
||||||
|
|
||||||
type Tab = TabsBase | (TabsBase & { render: () => JSX.Element });
|
type Tab = TabsBase | (TabsBase & { render: () => JSX.Element });
|
||||||
|
|
||||||
export const Tabs = ({ tabs, activeTab }: { tabs: readonly Tab[]; activeTab: Tab }): JSX.Element => (
|
export const Tabs = (): JSX.Element => {
|
||||||
|
const { openedTabs, activeTab } = useTabs();
|
||||||
|
return (
|
||||||
<div className="tabsManagerContainer">
|
<div className="tabsManagerContainer">
|
||||||
<div id="content" className="flexContainer hideOverflows">
|
<div id="content" className="flexContainer hideOverflows">
|
||||||
<div className="nav-tabs-margin">
|
<div className="nav-tabs-margin">
|
||||||
<ul className="nav nav-tabs level navTabHeight" id="navTabs" role="tablist">
|
<ul className="nav nav-tabs level navTabHeight" id="navTabs" role="tablist">
|
||||||
{tabs.map((tab) => (
|
{openedTabs.map((tab) => (
|
||||||
<TabNav key={tab.tabId} tab={tab} active={activeTab === tab} />
|
<TabNav key={tab.tabId} tab={tab} active={activeTab === tab} />
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="tabPanesContainer">
|
<div className="tabPanesContainer">
|
||||||
{tabs.map((tab) => (
|
{openedTabs.map((tab) => (
|
||||||
<TabPane key={tab.tabId} tab={tab} active={activeTab === tab} />
|
<TabPane key={tab.tabId} tab={tab} active={activeTab === tab} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
function TabNav({ tab, active }: { tab: Tab; active: boolean }) {
|
function TabNav({ tab, active }: { tab: Tab; active: boolean }) {
|
||||||
const [hovering, setHovering] = useState(false);
|
const [hovering, setHovering] = useState(false);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import * as ThemeUtility from "../../Common/ThemeUtility";
|
|||||||
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 { useNotificationConsole } from "../../hooks/useNotificationConsole";
|
import { useNotificationConsole } from "../../hooks/useNotificationConsole";
|
||||||
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
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";
|
||||||
@@ -11,7 +12,6 @@ import Explorer from "../Explorer";
|
|||||||
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
|
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { useSelectedNode } from "../useSelectedNode";
|
import { useSelectedNode } from "../useSelectedNode";
|
||||||
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
|
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
|
||||||
import { TabsManager } from "./TabsManager";
|
|
||||||
// TODO: Use specific actions for logging telemetry data
|
// TODO: Use specific actions for logging telemetry data
|
||||||
export default class TabsBase extends WaitsForTemplateViewModel {
|
export default class TabsBase extends WaitsForTemplateViewModel {
|
||||||
private static id = 0;
|
private static id = 0;
|
||||||
@@ -28,7 +28,6 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
public isExecutionError = ko.observable(false);
|
public isExecutionError = ko.observable(false);
|
||||||
public isExecuting = ko.observable(false);
|
public isExecuting = ko.observable(false);
|
||||||
public pendingNotification?: ko.Observable<DataModels.Notification>;
|
public pendingNotification?: ko.Observable<DataModels.Notification>;
|
||||||
public manager?: TabsManager;
|
|
||||||
protected _theme: string;
|
protected _theme: string;
|
||||||
public onLoadStartKey: number;
|
public onLoadStartKey: number;
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onCloseTabButtonClick(): void {
|
public onCloseTabButtonClick(): void {
|
||||||
this.manager?.closeTab(this);
|
useTabs.getState().closeTab(this);
|
||||||
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, {
|
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, {
|
||||||
tabName: this.constructor.name,
|
tabName: this.constructor.name,
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
@@ -70,7 +69,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): void {
|
public onTabClick(): void {
|
||||||
this.manager?.activateTab(this);
|
useTabs.getState().activateTab(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updateSelectedNode(): void {
|
protected updateSelectedNode(): void {
|
||||||
@@ -105,7 +104,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
|
|
||||||
/** @deprecated this is no longer observable, bind to comparisons with manager.activeTab() instead */
|
/** @deprecated this is no longer observable, bind to comparisons with manager.activeTab() instead */
|
||||||
public isActive() {
|
public isActive() {
|
||||||
return this === this.manager?.activeTab();
|
return this === useTabs.getState().activeTab;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onActivate(): void {
|
public onActivate(): void {
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
|
||||||
import TabsBase from "./TabsBase";
|
|
||||||
|
|
||||||
export class TabsManager {
|
|
||||||
public openedTabs = ko.observableArray<TabsBase>([]);
|
|
||||||
public activeTab = ko.observable<TabsBase>();
|
|
||||||
|
|
||||||
public activateNewTab(tab: TabsBase): void {
|
|
||||||
this.openedTabs.push(tab);
|
|
||||||
this.activateTab(tab);
|
|
||||||
}
|
|
||||||
|
|
||||||
public activateTab(tab: TabsBase): void {
|
|
||||||
if (this.openedTabs().includes(tab)) {
|
|
||||||
tab.manager = this;
|
|
||||||
this.activeTab(tab);
|
|
||||||
tab.onActivate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTabs(tabKind: ViewModels.CollectionTabKind, comparator?: (tab: TabsBase) => boolean): TabsBase[] {
|
|
||||||
return this.openedTabs().filter((tab) => tab.tabKind === tabKind && (!comparator || comparator(tab)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public refreshActiveTab(comparator: (tab: TabsBase) => boolean): void {
|
|
||||||
// ensures that the tab selects/highlights the right node based on resource tree expand/collapse state
|
|
||||||
this.activeTab() && comparator(this.activeTab()) && this.activeTab().onActivate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public closeTabsByComparator(comparator: (tab: TabsBase) => boolean): void {
|
|
||||||
this.openedTabs()
|
|
||||||
.filter(comparator)
|
|
||||||
.forEach((tab) => tab.onCloseTabButtonClick());
|
|
||||||
}
|
|
||||||
|
|
||||||
public closeTab(tab: TabsBase): void {
|
|
||||||
const tabIndex = this.openedTabs().indexOf(tab);
|
|
||||||
if (tabIndex !== -1) {
|
|
||||||
this.openedTabs.remove(tab);
|
|
||||||
tab.manager = undefined;
|
|
||||||
|
|
||||||
if (this.openedTabs().length === 0) {
|
|
||||||
this.activeTab(undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tab === this.activeTab()) {
|
|
||||||
const tabToTheRight = this.openedTabs()[tabIndex];
|
|
||||||
const lastOpenTab = this.openedTabs()[this.openedTabs().length - 1];
|
|
||||||
this.activateTab(tabToTheRight ?? lastOpenTab);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,7 @@ import { userContext } from "../../UserContext";
|
|||||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { NotebookTerminalComponent } from "../Controls/Notebook/NotebookTerminalComponent";
|
import { NotebookTerminalComponent } from "../Controls/Notebook/NotebookTerminalComponent";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
|
import { useNotebook } from "../Notebook/useNotebook";
|
||||||
import TabsBase from "./TabsBase";
|
import TabsBase from "./TabsBase";
|
||||||
|
|
||||||
export interface TerminalTabOptions extends ViewModels.TabOptions {
|
export interface TerminalTabOptions extends ViewModels.TabOptions {
|
||||||
@@ -54,8 +55,8 @@ export default class TerminalTab extends TabsBase {
|
|||||||
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
|
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
|
||||||
if (
|
if (
|
||||||
this.isTemplateReady() &&
|
this.isTemplateReady() &&
|
||||||
this.container.isNotebookEnabled() &&
|
useNotebook.getState().isNotebookEnabled &&
|
||||||
this.container.notebookServerInfo().notebookServerEndpoint
|
useNotebook.getState().notebookServerInfo?.notebookServerEndpoint
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -95,7 +96,7 @@ export default class TerminalTab extends TabsBase {
|
|||||||
throw new Error(`Terminal kind: ${options.kind} not supported`);
|
throw new Error(`Terminal kind: ${options.kind} not supported`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const info: DataModels.NotebookWorkspaceConnectionInfo = options.container.notebookServerInfo();
|
const info: DataModels.NotebookWorkspaceConnectionInfo = useNotebook.getState().notebookServerInfo;
|
||||||
return {
|
return {
|
||||||
authToken: info.authToken,
|
authToken: info.authToken,
|
||||||
notebookServerEndpoint: `${info.notebookServerEndpoint.replace(/\/+$/, "")}/${endpointSuffix}`,
|
notebookServerEndpoint: `${info.notebookServerEndpoint.replace(/\/+$/, "")}/${endpointSuffix}`,
|
||||||
|
|||||||
@@ -1,23 +1,19 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
import Explorer from "../Explorer";
|
import { container } from "../Controls/Settings/TestUtils";
|
||||||
import DocumentId from "../Tree/DocumentId";
|
import DocumentId from "../Tree/DocumentId";
|
||||||
import { container } from "./../Controls/Settings/TestUtils";
|
|
||||||
import DocumentsTab from "./DocumentsTab";
|
import DocumentsTab from "./DocumentsTab";
|
||||||
import { NewQueryTab } from "./QueryTab/QueryTab";
|
import { NewQueryTab } from "./QueryTab/QueryTab";
|
||||||
import { TabsManager } from "./TabsManager";
|
|
||||||
|
|
||||||
describe("Tabs manager tests", () => {
|
describe("useTabs tests", () => {
|
||||||
let tabsManager: TabsManager;
|
|
||||||
let explorer: Explorer;
|
|
||||||
let database: ViewModels.Database;
|
let database: ViewModels.Database;
|
||||||
let collection: ViewModels.Collection;
|
let collection: ViewModels.Collection;
|
||||||
let queryTab: NewQueryTab;
|
let queryTab: NewQueryTab;
|
||||||
let documentsTab: DocumentsTab;
|
let documentsTab: DocumentsTab;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer();
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
id: "test",
|
id: "test",
|
||||||
@@ -30,7 +26,6 @@ describe("Tabs manager tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
database = {
|
database = {
|
||||||
container: explorer,
|
|
||||||
id: ko.observable<string>("test"),
|
id: ko.observable<string>("test"),
|
||||||
isDatabaseShared: () => false,
|
isDatabaseShared: () => false,
|
||||||
} as ViewModels.Database;
|
} as ViewModels.Database;
|
||||||
@@ -38,7 +33,6 @@ describe("Tabs manager tests", () => {
|
|||||||
database.selectedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
|
database.selectedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
|
||||||
|
|
||||||
collection = {
|
collection = {
|
||||||
container: explorer,
|
|
||||||
databaseId: "test",
|
databaseId: "test",
|
||||||
id: ko.observable<string>("test"),
|
id: ko.observable<string>("test"),
|
||||||
} as ViewModels.Collection;
|
} as ViewModels.Collection;
|
||||||
@@ -76,63 +70,70 @@ describe("Tabs manager tests", () => {
|
|||||||
documentsTab.tabId = "2";
|
documentsTab.tabId = "2";
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => (tabsManager = new TabsManager()));
|
beforeEach(() => useTabs.setState({ openedTabs: [], activeTab: undefined }));
|
||||||
|
|
||||||
it("open new tabs", () => {
|
it("open new tabs", () => {
|
||||||
tabsManager.activateNewTab(queryTab);
|
const { activateNewTab } = useTabs.getState();
|
||||||
expect(tabsManager.openedTabs().length).toBe(1);
|
activateNewTab(queryTab);
|
||||||
expect(tabsManager.openedTabs()[0]).toEqual(queryTab);
|
let tabsState = useTabs.getState();
|
||||||
expect(tabsManager.activeTab()).toEqual(queryTab);
|
expect(tabsState.openedTabs.length).toBe(1);
|
||||||
|
expect(tabsState.openedTabs[0]).toEqual(queryTab);
|
||||||
|
expect(tabsState.activeTab).toEqual(queryTab);
|
||||||
expect(queryTab.isActive()).toBe(true);
|
expect(queryTab.isActive()).toBe(true);
|
||||||
|
|
||||||
tabsManager.activateNewTab(documentsTab);
|
activateNewTab(documentsTab);
|
||||||
expect(tabsManager.openedTabs().length).toBe(2);
|
tabsState = useTabs.getState();
|
||||||
expect(tabsManager.openedTabs()[1]).toEqual(documentsTab);
|
expect(tabsState.openedTabs.length).toBe(2);
|
||||||
expect(tabsManager.activeTab()).toEqual(documentsTab);
|
expect(tabsState.openedTabs[1]).toEqual(documentsTab);
|
||||||
|
expect(tabsState.activeTab).toEqual(documentsTab);
|
||||||
expect(queryTab.isActive()).toBe(false);
|
expect(queryTab.isActive()).toBe(false);
|
||||||
expect(documentsTab.isActive()).toBe(true);
|
expect(documentsTab.isActive()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("open existing tabs", () => {
|
it("open existing tabs", () => {
|
||||||
tabsManager.activateNewTab(queryTab);
|
const { activateNewTab, activateTab } = useTabs.getState();
|
||||||
tabsManager.activateNewTab(documentsTab);
|
activateNewTab(queryTab);
|
||||||
tabsManager.activateTab(queryTab);
|
activateNewTab(documentsTab);
|
||||||
expect(tabsManager.openedTabs().length).toBe(2);
|
activateTab(queryTab);
|
||||||
expect(tabsManager.activeTab()).toEqual(queryTab);
|
|
||||||
|
const { openedTabs, activeTab } = useTabs.getState();
|
||||||
|
expect(openedTabs.length).toBe(2);
|
||||||
|
expect(activeTab).toEqual(queryTab);
|
||||||
expect(queryTab.isActive()).toBe(true);
|
expect(queryTab.isActive()).toBe(true);
|
||||||
expect(documentsTab.isActive()).toBe(false);
|
expect(documentsTab.isActive()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("get tabs", () => {
|
it("get tabs", () => {
|
||||||
tabsManager.activateNewTab(queryTab);
|
const { activateNewTab, getTabs } = useTabs.getState();
|
||||||
tabsManager.activateNewTab(documentsTab);
|
activateNewTab(queryTab);
|
||||||
|
activateNewTab(documentsTab);
|
||||||
|
|
||||||
const queryTabs = tabsManager.getTabs(ViewModels.CollectionTabKind.Query);
|
const queryTabs = getTabs(ViewModels.CollectionTabKind.Query);
|
||||||
expect(queryTabs.length).toBe(1);
|
expect(queryTabs.length).toBe(1);
|
||||||
expect(queryTabs[0]).toEqual(queryTab);
|
expect(queryTabs[0]).toEqual(queryTab);
|
||||||
|
|
||||||
const documentsTabs = tabsManager.getTabs(
|
const documentsTabs = getTabs(ViewModels.CollectionTabKind.Documents, (tab) => tab.tabId === documentsTab.tabId);
|
||||||
ViewModels.CollectionTabKind.Documents,
|
|
||||||
(tab) => tab.tabId === documentsTab.tabId
|
|
||||||
);
|
|
||||||
expect(documentsTabs.length).toBe(1);
|
expect(documentsTabs.length).toBe(1);
|
||||||
expect(documentsTabs[0]).toEqual(documentsTab);
|
expect(documentsTabs[0]).toEqual(documentsTab);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("close tabs", () => {
|
it("close tabs", () => {
|
||||||
tabsManager.activateNewTab(queryTab);
|
const { activateNewTab, closeTab, closeTabsByComparator } = useTabs.getState();
|
||||||
tabsManager.activateNewTab(documentsTab);
|
activateNewTab(queryTab);
|
||||||
|
activateNewTab(documentsTab);
|
||||||
|
closeTab(documentsTab);
|
||||||
|
|
||||||
tabsManager.closeTab(documentsTab);
|
let tabsState = useTabs.getState();
|
||||||
expect(tabsManager.openedTabs().length).toBe(1);
|
expect(tabsState.openedTabs.length).toBe(1);
|
||||||
expect(tabsManager.openedTabs()[0]).toEqual(queryTab);
|
expect(tabsState.openedTabs[0]).toEqual(queryTab);
|
||||||
expect(tabsManager.activeTab()).toEqual(queryTab);
|
expect(tabsState.activeTab).toEqual(queryTab);
|
||||||
expect(queryTab.isActive()).toBe(true);
|
expect(queryTab.isActive()).toBe(true);
|
||||||
expect(documentsTab.isActive()).toBe(false);
|
expect(documentsTab.isActive()).toBe(false);
|
||||||
|
|
||||||
tabsManager.closeTabsByComparator((tab) => tab.tabId === queryTab.tabId);
|
closeTabsByComparator((tab) => tab.tabId === queryTab.tabId);
|
||||||
expect(tabsManager.openedTabs().length).toBe(0);
|
tabsState = useTabs.getState();
|
||||||
expect(tabsManager.activeTab()).toEqual(undefined);
|
expect(tabsState.openedTabs.length).toBe(0);
|
||||||
|
expect(tabsState.activeTab).toEqual(undefined);
|
||||||
expect(queryTab.isActive()).toBe(false);
|
expect(queryTab.isActive()).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -4,18 +4,12 @@ import Collection from "./Collection";
|
|||||||
jest.mock("monaco-editor");
|
jest.mock("monaco-editor");
|
||||||
|
|
||||||
describe("Collection", () => {
|
describe("Collection", () => {
|
||||||
function generateCollection(
|
const generateCollection = (container: Explorer, databaseId: string, data: DataModels.Collection): Collection =>
|
||||||
container: Explorer,
|
new Collection(container, databaseId, data);
|
||||||
databaseId: string,
|
|
||||||
data: DataModels.Collection,
|
|
||||||
offer: DataModels.Offer
|
|
||||||
): Collection {
|
|
||||||
return new Collection(container, databaseId, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateMockCollectionsDataModelWithPartitionKey(
|
const generateMockCollectionsDataModelWithPartitionKey = (
|
||||||
partitionKey: DataModels.PartitionKey
|
partitionKey: DataModels.PartitionKey
|
||||||
): DataModels.Collection {
|
): DataModels.Collection => {
|
||||||
return {
|
return {
|
||||||
defaultTtl: 1,
|
defaultTtl: 1,
|
||||||
indexingPolicy: {} as DataModels.IndexingPolicy,
|
indexingPolicy: {} as DataModels.IndexingPolicy,
|
||||||
@@ -26,13 +20,12 @@ describe("Collection", () => {
|
|||||||
_ts: 1,
|
_ts: 1,
|
||||||
id: "",
|
id: "",
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
function generateMockCollectionWithDataModel(data: DataModels.Collection): Collection {
|
const generateMockCollectionWithDataModel = (data: DataModels.Collection): Collection => {
|
||||||
const mockContainer = {} as Explorer;
|
const mockContainer = {} as Explorer;
|
||||||
|
return generateCollection(mockContainer, "abc", data);
|
||||||
return generateCollection(mockContainer, "abc", data, {} as DataModels.Offer);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
describe("Partition key path parsing", () => {
|
describe("Partition key path parsing", () => {
|
||||||
let collection: Collection;
|
let collection: Collection;
|
||||||
@@ -88,7 +81,7 @@ describe("Collection", () => {
|
|||||||
kind: "Hash",
|
kind: "Hash",
|
||||||
});
|
});
|
||||||
collection = generateMockCollectionWithDataModel(collectionsDataModel);
|
collection = generateMockCollectionWithDataModel(collectionsDataModel);
|
||||||
expect(collection.partitionKeyPropertyHeader).toBeNull;
|
expect(collection.partitionKeyPropertyHeader).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
|||||||
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 { UploadDetailsRecord } from "../../Contracts/ViewModels";
|
import { UploadDetailsRecord } from "../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
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";
|
||||||
@@ -31,7 +32,7 @@ import MongoDocumentsTab from "../Tabs/MongoDocumentsTab";
|
|||||||
import { NewMongoQueryTab } from "../Tabs/MongoQueryTab/MongoQueryTab";
|
import { NewMongoQueryTab } from "../Tabs/MongoQueryTab/MongoQueryTab";
|
||||||
import { NewMongoShellTab } from "../Tabs/MongoShellTab/MongoShellTab";
|
import { NewMongoShellTab } from "../Tabs/MongoShellTab/MongoShellTab";
|
||||||
import { NewQueryTab } from "../Tabs/QueryTab/QueryTab";
|
import { NewQueryTab } from "../Tabs/QueryTab/QueryTab";
|
||||||
import QueryTablesTab from "../Tabs/QueryTablesTab";
|
import { NewQueryTablesTab } from "../Tabs/QueryTablesTab/NewQueryTablesTab";
|
||||||
import { CollectionSettingsTabV2 } from "../Tabs/SettingsTabV2";
|
import { CollectionSettingsTabV2 } from "../Tabs/SettingsTabV2";
|
||||||
import { useDatabases } from "../useDatabases";
|
import { useDatabases } from "../useDatabases";
|
||||||
import { useSelectedNode } from "../useSelectedNode";
|
import { useSelectedNode } from "../useSelectedNode";
|
||||||
@@ -239,7 +240,9 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
this.expandCollection();
|
this.expandCollection();
|
||||||
}
|
}
|
||||||
useCommandBar.getState().setContextButtons([]);
|
useCommandBar.getState().setContextButtons([]);
|
||||||
this.container.tabsManager.refreshActiveTab(
|
useTabs
|
||||||
|
.getState()
|
||||||
|
.refreshActiveTab(
|
||||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -288,14 +291,16 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
|
|
||||||
const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
|
const documentsTabs: DocumentsTab[] = useTabs
|
||||||
|
.getState()
|
||||||
|
.getTabs(
|
||||||
ViewModels.CollectionTabKind.Documents,
|
ViewModels.CollectionTabKind.Documents,
|
||||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
) as DocumentsTab[];
|
) as DocumentsTab[];
|
||||||
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
|
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
|
||||||
|
|
||||||
if (documentsTab) {
|
if (documentsTab) {
|
||||||
this.container.tabsManager.activateTab(documentsTab);
|
useTabs.getState().activateTab(documentsTab);
|
||||||
} else {
|
} else {
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||||
databaseName: this.databaseId,
|
databaseName: this.databaseId,
|
||||||
@@ -317,7 +322,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(documentsTab);
|
useTabs.getState().activateNewTab(documentsTab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,14 +338,16 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
|
|
||||||
const conflictsTabs: ConflictsTab[] = this.container.tabsManager.getTabs(
|
const conflictsTabs: ConflictsTab[] = useTabs
|
||||||
|
.getState()
|
||||||
|
.getTabs(
|
||||||
ViewModels.CollectionTabKind.Conflicts,
|
ViewModels.CollectionTabKind.Conflicts,
|
||||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
) as ConflictsTab[];
|
) as ConflictsTab[];
|
||||||
let conflictsTab: ConflictsTab = conflictsTabs && conflictsTabs[0];
|
let conflictsTab: ConflictsTab = conflictsTabs && conflictsTabs[0];
|
||||||
|
|
||||||
if (conflictsTab) {
|
if (conflictsTab) {
|
||||||
this.container.tabsManager.activateTab(conflictsTab);
|
useTabs.getState().activateTab(conflictsTab);
|
||||||
} else {
|
} else {
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||||
databaseName: this.databaseId,
|
databaseName: this.databaseId,
|
||||||
@@ -362,7 +369,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(conflictsTab);
|
useTabs.getState().activateNewTab(conflictsTab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,14 +391,16 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryTablesTabs: QueryTablesTab[] = this.container.tabsManager.getTabs(
|
const queryTablesTabs: NewQueryTablesTab[] = useTabs
|
||||||
|
.getState()
|
||||||
|
.getTabs(
|
||||||
ViewModels.CollectionTabKind.QueryTables,
|
ViewModels.CollectionTabKind.QueryTables,
|
||||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
) as QueryTablesTab[];
|
) as NewQueryTablesTab[];
|
||||||
let queryTablesTab: QueryTablesTab = queryTablesTabs && queryTablesTabs[0];
|
let queryTablesTab: NewQueryTablesTab = queryTablesTabs && queryTablesTabs[0];
|
||||||
|
|
||||||
if (queryTablesTab) {
|
if (queryTablesTab) {
|
||||||
this.container.tabsManager.activateTab(queryTablesTab);
|
useTabs.getState().activateTab(queryTablesTab);
|
||||||
} else {
|
} else {
|
||||||
this.documentIds([]);
|
this.documentIds([]);
|
||||||
let title = `Entities`;
|
let title = `Entities`;
|
||||||
@@ -406,16 +415,16 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
tabTitle: title,
|
tabTitle: title,
|
||||||
});
|
});
|
||||||
|
|
||||||
queryTablesTab = new QueryTablesTab({
|
queryTablesTab = new NewQueryTablesTab({
|
||||||
tabKind: ViewModels.CollectionTabKind.QueryTables,
|
tabKind: ViewModels.CollectionTabKind.QueryTables,
|
||||||
title: title,
|
title: title,
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
collection: this,
|
collection: this,
|
||||||
node: this,
|
node: this,
|
||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
});
|
}, { container: this.container });
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(queryTablesTab);
|
useTabs.getState().activateNewTab(queryTablesTab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,14 +440,16 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
|
|
||||||
const graphTabs: GraphTab[] = this.container.tabsManager.getTabs(
|
const graphTabs: GraphTab[] = useTabs
|
||||||
|
.getState()
|
||||||
|
.getTabs(
|
||||||
ViewModels.CollectionTabKind.Graph,
|
ViewModels.CollectionTabKind.Graph,
|
||||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
) as GraphTab[];
|
) as GraphTab[];
|
||||||
let graphTab: GraphTab = graphTabs && graphTabs[0];
|
let graphTab: GraphTab = graphTabs && graphTabs[0];
|
||||||
|
|
||||||
if (graphTab) {
|
if (graphTab) {
|
||||||
this.container.tabsManager.activateTab(graphTab);
|
useTabs.getState().activateTab(graphTab);
|
||||||
} else {
|
} else {
|
||||||
this.documentIds([]);
|
this.documentIds([]);
|
||||||
const title = "Graph";
|
const title = "Graph";
|
||||||
@@ -466,7 +477,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(graphTab);
|
useTabs.getState().activateNewTab(graphTab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,14 +493,16 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mongoDocumentsTabs: MongoDocumentsTab[] = this.container.tabsManager.getTabs(
|
const mongoDocumentsTabs: MongoDocumentsTab[] = useTabs
|
||||||
|
.getState()
|
||||||
|
.getTabs(
|
||||||
ViewModels.CollectionTabKind.Documents,
|
ViewModels.CollectionTabKind.Documents,
|
||||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
) as MongoDocumentsTab[];
|
) as MongoDocumentsTab[];
|
||||||
let mongoDocumentsTab: MongoDocumentsTab = mongoDocumentsTabs && mongoDocumentsTabs[0];
|
let mongoDocumentsTab: MongoDocumentsTab = mongoDocumentsTabs && mongoDocumentsTabs[0];
|
||||||
|
|
||||||
if (mongoDocumentsTab) {
|
if (mongoDocumentsTab) {
|
||||||
this.container.tabsManager.activateTab(mongoDocumentsTab);
|
useTabs.getState().activateTab(mongoDocumentsTab);
|
||||||
} else {
|
} else {
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||||
databaseName: this.databaseId,
|
databaseName: this.databaseId,
|
||||||
@@ -510,7 +523,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
node: this,
|
node: this,
|
||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
});
|
});
|
||||||
this.container.tabsManager.activateNewTab(mongoDocumentsTab);
|
useTabs.getState().activateNewTab(mongoDocumentsTab);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -525,13 +538,13 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const tab of this.container.tabsManager.openedTabs()) {
|
for (const tab of useTabs.getState().openedTabs) {
|
||||||
if (
|
if (
|
||||||
tab instanceof SchemaAnalyzerTab &&
|
tab instanceof SchemaAnalyzerTab &&
|
||||||
tab.collection?.databaseId === this.databaseId &&
|
tab.collection?.databaseId === this.databaseId &&
|
||||||
tab.collection?.id() === this.id()
|
tab.collection?.id() === this.id()
|
||||||
) {
|
) {
|
||||||
return this.container.tabsManager.activateTab(tab);
|
return useTabs.getState().activateTab(tab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,7 +555,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
tabTitle: "Schema",
|
tabTitle: "Schema",
|
||||||
});
|
});
|
||||||
this.documentIds([]);
|
this.documentIds([]);
|
||||||
this.container.tabsManager.activateNewTab(
|
useTabs.getState().activateNewTab(
|
||||||
new SchemaAnalyzerTab({
|
new SchemaAnalyzerTab({
|
||||||
account: userContext.databaseAccount,
|
account: userContext.databaseAccount,
|
||||||
masterKey: userContext.masterKey || "",
|
masterKey: userContext.masterKey || "",
|
||||||
@@ -559,6 +572,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
|
|
||||||
public onSettingsClick = async (): Promise<void> => {
|
public onSettingsClick = async (): Promise<void> => {
|
||||||
useSelectedNode.getState().setSelectedNode(this);
|
useSelectedNode.getState().setSelectedNode(this);
|
||||||
|
await this.loadOffer();
|
||||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Settings);
|
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Settings);
|
||||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||||
description: "Settings node",
|
description: "Settings node",
|
||||||
@@ -570,12 +584,9 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
||||||
const matchingTabs = this.container.tabsManager.getTabs(
|
const matchingTabs = useTabs.getState().getTabs(ViewModels.CollectionTabKind.CollectionSettingsV2, (tab) => {
|
||||||
ViewModels.CollectionTabKind.CollectionSettingsV2,
|
|
||||||
(tab) => {
|
|
||||||
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
|
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const traceStartData = {
|
const traceStartData = {
|
||||||
databaseName: this.databaseId,
|
databaseName: this.databaseId,
|
||||||
@@ -607,15 +618,15 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
settingsTabOptions.onLoadStartKey = startKey;
|
settingsTabOptions.onLoadStartKey = startKey;
|
||||||
settingsTabOptions.tabKind = ViewModels.CollectionTabKind.CollectionSettingsV2;
|
settingsTabOptions.tabKind = ViewModels.CollectionTabKind.CollectionSettingsV2;
|
||||||
settingsTabV2 = new CollectionSettingsTabV2(settingsTabOptions);
|
settingsTabV2 = new CollectionSettingsTabV2(settingsTabOptions);
|
||||||
this.container.tabsManager.activateNewTab(settingsTabV2);
|
useTabs.getState().activateNewTab(settingsTabV2);
|
||||||
} else {
|
} else {
|
||||||
this.container.tabsManager.activateTab(settingsTabV2);
|
useTabs.getState().activateTab(settingsTabV2);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
||||||
const collection: ViewModels.Collection = source.collection || source;
|
const collection: ViewModels.Collection = source.collection || source;
|
||||||
const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
||||||
const title = "Query " + id;
|
const title = "Query " + id;
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||||
databaseName: this.databaseId,
|
databaseName: this.databaseId,
|
||||||
@@ -625,7 +636,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
tabTitle: title,
|
tabTitle: title,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(
|
useTabs.getState().activateNewTab(
|
||||||
new NewQueryTab(
|
new NewQueryTab(
|
||||||
{
|
{
|
||||||
tabKind: ViewModels.CollectionTabKind.Query,
|
tabKind: ViewModels.CollectionTabKind.Query,
|
||||||
@@ -644,7 +655,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
|
|
||||||
public onNewMongoQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
public onNewMongoQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
||||||
const collection: ViewModels.Collection = source.collection || source;
|
const collection: ViewModels.Collection = source.collection || source;
|
||||||
const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
||||||
|
|
||||||
const title = "Query " + id;
|
const title = "Query " + id;
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||||
@@ -671,11 +682,11 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(newMongoQueryTab);
|
useTabs.getState().activateNewTab(newMongoQueryTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onNewGraphClick() {
|
public onNewGraphClick() {
|
||||||
const id: number = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Graph).length + 1;
|
const id: number = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Graph).length + 1;
|
||||||
const title: string = "Graph Query " + id;
|
const title: string = "Graph Query " + id;
|
||||||
|
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||||
@@ -701,13 +712,11 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(graphTab);
|
useTabs.getState().activateNewTab(graphTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onNewMongoShellClick() {
|
public onNewMongoShellClick() {
|
||||||
const mongoShellTabs = this.container.tabsManager.getTabs(
|
const mongoShellTabs = useTabs.getState().getTabs(ViewModels.CollectionTabKind.MongoShell) as NewMongoShellTab[];
|
||||||
ViewModels.CollectionTabKind.MongoShell
|
|
||||||
) as NewMongoShellTab[];
|
|
||||||
|
|
||||||
let index = 1;
|
let index = 1;
|
||||||
if (mongoShellTabs.length > 0) {
|
if (mongoShellTabs.length > 0) {
|
||||||
@@ -728,15 +737,15 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(mongoShellTab);
|
useTabs.getState().activateNewTab(mongoShellTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onNewStoredProcedureClick(source: ViewModels.Collection, event: MouseEvent) {
|
public onNewStoredProcedureClick(source: ViewModels.Collection, event: MouseEvent) {
|
||||||
StoredProcedure.create(source, event);
|
StoredProcedure.create(source, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onNewUserDefinedFunctionClick(source: ViewModels.Collection, event: MouseEvent) {
|
public onNewUserDefinedFunctionClick(source: ViewModels.Collection) {
|
||||||
UserDefinedFunction.create(source, event);
|
UserDefinedFunction.create(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onNewTriggerClick(source: ViewModels.Collection, event: MouseEvent) {
|
public onNewTriggerClick(source: ViewModels.Collection, event: MouseEvent) {
|
||||||
@@ -786,7 +795,9 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
} else {
|
} else {
|
||||||
this.expandStoredProcedures();
|
this.expandStoredProcedures();
|
||||||
}
|
}
|
||||||
this.container.tabsManager.refreshActiveTab(
|
useTabs
|
||||||
|
.getState()
|
||||||
|
.refreshActiveTab(
|
||||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -845,7 +856,9 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
} else {
|
} else {
|
||||||
this.expandUserDefinedFunctions();
|
this.expandUserDefinedFunctions();
|
||||||
}
|
}
|
||||||
this.container.tabsManager.refreshActiveTab(
|
useTabs
|
||||||
|
.getState()
|
||||||
|
.refreshActiveTab(
|
||||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -904,7 +917,9 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
} else {
|
} else {
|
||||||
this.expandTriggers();
|
this.expandTriggers();
|
||||||
}
|
}
|
||||||
this.container.tabsManager.refreshActiveTab(
|
useTabs
|
||||||
|
.getState()
|
||||||
|
.refreshActiveTab(
|
||||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
import { HttpStatusCodes } from "../../Common/Constants";
|
import { HttpStatusCodes } from "../../Common/Constants";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { JunoClient } from "../../Juno/JunoClient";
|
import { JunoClient } from "../../Juno/JunoClient";
|
||||||
|
import { Features } from "../../Platform/Hosted/extractFeatures";
|
||||||
import { updateUserContext, userContext } from "../../UserContext";
|
import { updateUserContext, userContext } from "../../UserContext";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import Database from "./Database";
|
import Database from "./Database";
|
||||||
@@ -31,11 +31,10 @@ updateUserContext({
|
|||||||
|
|
||||||
describe("Add Schema", () => {
|
describe("Add Schema", () => {
|
||||||
it("should not call requestSchema or getSchema if analyticalStorageTtl is undefined", () => {
|
it("should not call requestSchema or getSchema if analyticalStorageTtl is undefined", () => {
|
||||||
const collection: DataModels.Collection = {} as DataModels.Collection;
|
const collection: DataModels.Collection = { id: "fakeId" } as DataModels.Collection;
|
||||||
collection.analyticalStorageTtl = undefined;
|
collection.analyticalStorageTtl = undefined;
|
||||||
const database = new Database(createMockContainer(), { id: "fakeId" });
|
const database = new Database(createMockContainer(), collection);
|
||||||
database.container = createMockContainer();
|
database.container = createMockContainer();
|
||||||
database.container.isSchemaEnabled = ko.computed<boolean>(() => false);
|
|
||||||
|
|
||||||
database.junoClient = new JunoClient();
|
database.junoClient = new JunoClient();
|
||||||
database.junoClient.requestSchema = jest.fn();
|
database.junoClient.requestSchema = jest.fn();
|
||||||
@@ -47,12 +46,16 @@ describe("Add Schema", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should call requestSchema or getSchema if analyticalStorageTtl is not undefined", () => {
|
it("should call requestSchema or getSchema if analyticalStorageTtl is not undefined", () => {
|
||||||
const collection: DataModels.Collection = { id: "fakeId" } as DataModels.Collection;
|
const collection: DataModels.Collection = {} as DataModels.Collection;
|
||||||
collection.analyticalStorageTtl = 0;
|
collection.analyticalStorageTtl = 0;
|
||||||
|
|
||||||
const database = new Database(createMockContainer(), {});
|
const database = new Database(createMockContainer(), collection);
|
||||||
database.container = createMockContainer();
|
database.container = createMockContainer();
|
||||||
database.container.isSchemaEnabled = ko.computed<boolean>(() => true);
|
updateUserContext({
|
||||||
|
features: {
|
||||||
|
enableSchema: true,
|
||||||
|
} as Features,
|
||||||
|
});
|
||||||
|
|
||||||
database.junoClient = new JunoClient();
|
database.junoClient = new JunoClient();
|
||||||
database.junoClient.requestSchema = jest.fn();
|
database.junoClient.requestSchema = jest.fn();
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user