Compare commits
10 Commits
eslint/fix
...
users/srna
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c7f4d1ddf | ||
|
|
78e5c88e3c | ||
|
|
c0bd74ce1b | ||
|
|
c04ba728ef | ||
|
|
127b16cfc8 | ||
|
|
0f281c7a64 | ||
|
|
659d5a6677 | ||
|
|
5f66f113af | ||
|
|
b1c238f43a | ||
|
|
445d2650a2 |
15
.env.example
@@ -1 +1,16 @@
|
||||
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
|
||||
161
.eslintignore
@@ -21,8 +21,16 @@ src/Common/MongoUtility.ts
|
||||
src/Common/NotificationsClientBase.ts
|
||||
src/Common/QueriesClient.ts
|
||||
src/Common/Splitter.ts
|
||||
src/Config.ts
|
||||
src/Contracts/ActionContracts.ts
|
||||
src/Contracts/DataModels.ts
|
||||
src/Contracts/Diagnostics.ts
|
||||
src/Contracts/ExplorerContracts.ts
|
||||
src/Contracts/Versions.ts
|
||||
src/Contracts/ViewModels.ts
|
||||
src/Controls/Heatmap/Heatmap.test.ts
|
||||
src/Controls/Heatmap/Heatmap.ts
|
||||
src/Controls/Heatmap/HeatmapDatatypes.ts
|
||||
src/Definitions/datatables.d.ts
|
||||
src/Definitions/gif.d.ts
|
||||
src/Definitions/globals.d.ts
|
||||
@@ -36,14 +44,34 @@ src/Definitions/png.d.ts
|
||||
src/Definitions/svg.d.ts
|
||||
src/Explorer/ComponentRegisterer.test.ts
|
||||
src/Explorer/ComponentRegisterer.ts
|
||||
src/Explorer/Controls/CollapsiblePanel/CollapsiblePanelComponent.ts
|
||||
src/Explorer/Controls/DiffEditor/DiffEditorComponent.ts
|
||||
|
||||
src/Explorer/Controls/DynamicList/DynamicList.test.ts
|
||||
src/Explorer/Controls/DynamicList/DynamicListComponent.ts
|
||||
src/Explorer/Controls/Editor/EditorComponent.ts
|
||||
src/Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.ts
|
||||
src/Explorer/Controls/InputTypeahead/InputTypeahead.ts
|
||||
src/Explorer/Controls/JsonEditor/JsonEditorComponent.ts
|
||||
src/Explorer/Controls/Notebook/NotebookAppMessageHandler.ts
|
||||
src/Explorer/Controls/ThroughputInput/ThroughputInputComponent.ts
|
||||
src/Explorer/Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3.ts
|
||||
src/Explorer/Controls/Toolbar/IToolbarAction.ts
|
||||
src/Explorer/Controls/Toolbar/IToolbarDisplayable.ts
|
||||
src/Explorer/Controls/Toolbar/IToolbarDropDown.ts
|
||||
src/Explorer/Controls/Toolbar/IToolbarItem.ts
|
||||
src/Explorer/Controls/Toolbar/IToolbarSeperator.ts
|
||||
src/Explorer/Controls/Toolbar/IToolbarToggle.ts
|
||||
src/Explorer/Controls/Toolbar/KeyCodes.ts
|
||||
src/Explorer/Controls/Toolbar/Toolbar.ts
|
||||
src/Explorer/Controls/Toolbar/ToolbarAction.ts
|
||||
src/Explorer/Controls/Toolbar/ToolbarDropDown.ts
|
||||
src/Explorer/Controls/Toolbar/ToolbarToggle.ts
|
||||
src/Explorer/Controls/Toolbar/Utilities.ts
|
||||
src/Explorer/DataSamples/ContainerSampleGenerator.test.ts
|
||||
src/Explorer/DataSamples/ContainerSampleGenerator.ts
|
||||
src/Explorer/DataSamples/DataSamplesUtil.test.ts
|
||||
src/Explorer/DataSamples/DataSamplesUtil.ts
|
||||
src/Explorer/Explorer.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.test.ts
|
||||
src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.ts
|
||||
src/Explorer/Graph/GraphExplorerComponent/D3ForceGraph.test.ts
|
||||
@@ -55,6 +83,11 @@ src/Explorer/Graph/GraphExplorerComponent/GremlinClient.test.ts
|
||||
src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts
|
||||
src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.test.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/MostRecentActivity/MostRecentActivity.ts
|
||||
src/Explorer/Notebook/NotebookClientV2.ts
|
||||
@@ -72,75 +105,199 @@ src/Explorer/Notebook/NotebookContentClient.ts
|
||||
src/Explorer/Notebook/NotebookContentItem.ts
|
||||
src/Explorer/Notebook/NotebookUtil.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/EntityPropertyValidationCommon.ts
|
||||
src/Explorer/Panes/Tables/Validators/EntityPropertyValueValidator.ts
|
||||
src/Explorer/SplashScreen/SplashScreen.test.ts
|
||||
src/Explorer/Tables/Constants.ts
|
||||
src/Explorer/Tables/DataTable/CacheBase.ts
|
||||
src/Explorer/Tables/DataTable/DataTableBindingManager.ts
|
||||
src/Explorer/Tables/DataTable/DataTableBuilder.ts
|
||||
src/Explorer/Tables/DataTable/DataTableContextMenu.ts
|
||||
src/Explorer/Tables/DataTable/DataTableOperationManager.ts
|
||||
src/Explorer/Tables/DataTable/DataTableOperations.ts
|
||||
src/Explorer/Tables/DataTable/DataTableViewModel.ts
|
||||
src/Explorer/Tables/DataTable/TableCommands.ts
|
||||
src/Explorer/Tables/DataTable/TableEntityCache.ts
|
||||
src/Explorer/Tables/DataTable/TableEntityListViewModel.ts
|
||||
src/Explorer/Tables/Entities.ts
|
||||
src/Explorer/Tables/QueryBuilder/ClauseGroup.ts
|
||||
src/Explorer/Tables/QueryBuilder/ClauseGroupViewModel.ts
|
||||
src/Explorer/Tables/QueryBuilder/CustomTimestampHelper.ts
|
||||
src/Explorer/Tables/QueryBuilder/QueryBuilderViewModel.ts
|
||||
src/Explorer/Tables/QueryBuilder/QueryClauseViewModel.ts
|
||||
src/Explorer/Tables/TableDataClient.ts
|
||||
src/Explorer/Tables/TableEntityProcessor.ts
|
||||
src/Explorer/Tables/Utilities.ts
|
||||
src/Explorer/Tabs/ConflictsTab.ts
|
||||
src/Explorer/Tabs/DatabaseSettingsTab.ts
|
||||
src/Explorer/Tabs/DocumentsTab.test.ts
|
||||
src/Explorer/Tabs/DocumentsTab.ts
|
||||
src/Explorer/Tabs/GraphTab.ts
|
||||
src/Explorer/Tabs/MongoDocumentsTab.ts
|
||||
# src/Explorer/Tabs/MongoQueryTab.ts
|
||||
# src/Explorer/Tabs/MongoShellTab.ts
|
||||
src/Explorer/Tabs/NotebookV2Tab.ts
|
||||
src/Explorer/Tabs/ScriptTabBase.ts
|
||||
# src/Explorer/Tabs/StoredProcedureTab.ts
|
||||
src/Explorer/Tabs/TabComponents.ts
|
||||
src/Explorer/Tabs/TabsBase.ts
|
||||
src/Explorer/Tabs/TriggerTab.ts
|
||||
src/Explorer/Tabs/UserDefinedFunctionTab.ts
|
||||
src/Explorer/Tree/AccessibleVerticalList.ts
|
||||
src/Explorer/Tree/Collection.test.ts
|
||||
src/Explorer/Tree/Collection.ts
|
||||
src/Explorer/Tree/ConflictId.ts
|
||||
src/Explorer/Tree/Database.ts
|
||||
src/Explorer/Tree/DocumentId.ts
|
||||
src/Explorer/Tree/ObjectId.ts
|
||||
src/Explorer/Tree/ResourceTokenCollection.ts
|
||||
src/Explorer/Tree/StoredProcedure.ts
|
||||
src/Explorer/Tree/TreeComponents.ts
|
||||
src/Explorer/Tree/Trigger.ts
|
||||
src/Explorer/Tree/UserDefinedFunction.ts
|
||||
src/Explorer/WaitsForTemplateViewModel.ts
|
||||
src/GitHub/GitHubClient.test.ts
|
||||
src/GitHub/GitHubClient.ts
|
||||
src/GitHub/GitHubConnector.ts
|
||||
src/GitHub/GitHubContentProvider.test.ts
|
||||
src/GitHub/GitHubContentProvider.ts
|
||||
src/GitHub/GitHubOAuthService.ts
|
||||
src/HostedExplorer.ts
|
||||
src/Index.ts
|
||||
src/Juno/JunoClient.test.ts
|
||||
src/Juno/JunoClient.ts
|
||||
src/Main.ts
|
||||
src/NotebookWorkspaceManager/NotebookWorkspaceManager.ts
|
||||
src/NotebookWorkspaceManager/NotebookWorkspaceResourceProviderMockClients.ts
|
||||
src/Platform/Emulator/DataAccessUtility.ts
|
||||
src/Platform/Emulator/ExplorerFactory.ts
|
||||
src/Platform/Emulator/Main.ts
|
||||
src/Platform/Emulator/NotificationsClient.ts
|
||||
src/Platform/Hosted/ArmResourceUtils.ts
|
||||
src/Platform/Hosted/Authorization.ts
|
||||
src/Platform/Hosted/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/ResourceProvider/IResourceProviderClient.test.ts
|
||||
src/ResourceProvider/IResourceProviderClient.ts
|
||||
src/ResourceProvider/ResourceProviderClient.ts
|
||||
src/ResourceProvider/ResourceProviderClientFactory.ts
|
||||
src/Shared/Constants.ts
|
||||
src/Shared/DefaultExperienceUtility.test.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/SparkClusterManager/ArcadiaResourceManager.ts
|
||||
src/SparkClusterManager/SparkClusterManager.ts
|
||||
src/Terminal/JupyterLabAppFactory.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/global.d.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/Notebook/NotebookTerminalComponent.test.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/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/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.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/LeftPaneComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.test.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/QueryContainerComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNeighborsComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.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/NotebookComponentBootstrapper.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/NotebookRenderer/AzureTheme.tsx
|
||||
src/Explorer/Notebook/NotebookRenderer/NotebookReadOnlyRenderer.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/hijack-scroll/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/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/ResourceTreeAdapterForResourceToken.tsx
|
||||
src/GalleryViewer/Cards/GalleryCardComponent.tsx
|
||||
src/GalleryViewer/GalleryViewer.tsx
|
||||
src/GalleryViewer/GalleryViewerComponent.tsx
|
||||
__mocks__/monaco-editor.ts
|
||||
src/Explorer/Tree/ResourceTree.tsx
|
||||
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx
|
||||
@@ -39,6 +39,7 @@ module.exports = {
|
||||
"@typescript-eslint/switch-exhaustiveness-check": "error",
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"@typescript-eslint/no-extraneous-class": "error",
|
||||
"no-null/no-null": "error",
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
|
||||
eqeqeq: "error",
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
@@ -143,7 +143,7 @@ jobs:
|
||||
- ./test/mongo/container.spec.ts
|
||||
- ./test/mongo/container32.spec.ts
|
||||
- ./test/selfServe/selfServeExample.spec.ts
|
||||
# - ./test/notebooks/upload.spec.ts // TEMP disabled since notebooks service is off
|
||||
- ./test/notebooks/upload.spec.ts
|
||||
- ./test/sql/resourceToken.spec.ts
|
||||
- ./test/tables/container.spec.ts
|
||||
steps:
|
||||
|
||||
3
.vscode/settings.json
vendored
@@ -22,6 +22,5 @@
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true,
|
||||
"source.organizeImports": true
|
||||
},
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com"
|
||||
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com",
|
||||
"enableSchemaAnalyzer": true
|
||||
}
|
||||
|
Before Width: | Height: | Size: 842 B After Width: | Height: | Size: 842 B |
|
Before Width: | Height: | Size: 371 B After Width: | Height: | Size: 371 B |
@@ -1,8 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
version="1.1"
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path
|
||||
d="M14,2.691l-5.301,5.309l5.301,5.309l-0.691,0.691l-5.309,-5.301l-5.309,5.301l-0.691,-0.691l5.301,-5.309l-5.301,-5.309l0.691,-0.691l5.309,5.301l5.309,-5.301l0.691,0.691Z"
|
||||
transform="scale(0.5)" fill="#000" stroke="#000">
|
||||
<path
|
||||
d="M14,2.691l-5.301,5.309l5.301,5.309l-0.691,0.691l-5.309,-5.301l-5.309,5.301l-0.691,-0.691l5.301,-5.309l-5.301,-5.309l0.691,-0.691l5.309,5.301l5.309,-5.301l0.691,0.691Z"
|
||||
transform="scale(0.5)"
|
||||
fill="#000"
|
||||
stroke="#CCC"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 449 B After Width: | Height: | Size: 503 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -37,8 +37,8 @@ module.exports = {
|
||||
global: {
|
||||
branches: 25,
|
||||
functions: 25,
|
||||
lines: 29,
|
||||
statements: 29,
|
||||
lines: 30,
|
||||
statements: 30,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -129,8 +129,6 @@ module.exports = {
|
||||
// The test environment that will be used for testing
|
||||
// testEnvironment: "jest-environment-jsdom",
|
||||
|
||||
modulePaths: ["node_modules", "<rootDir>/src"],
|
||||
|
||||
// Options that will be passed to the testEnvironment
|
||||
// testEnvironmentOptions: {},
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
@DataExplorerFont: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
|
||||
@SemiboldFont: "Segoe UI Semibold", "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
|
||||
@GrayScale: "grayscale()";
|
||||
|
||||
@xSmallFontSize: 4px;
|
||||
@smallFontSize: 8px;
|
||||
|
||||
@@ -2357,8 +2357,6 @@ a:link {
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
min-height: 300px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
@@ -2834,8 +2832,6 @@ a:link {
|
||||
|
||||
#explorerNotificationConsole {
|
||||
z-index: 1000;
|
||||
overflow-y: auto;
|
||||
overflow-x: clip;
|
||||
}
|
||||
|
||||
.uniqueIndexesContainer {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
.dataResourceTree {
|
||||
margin-left: @MediumSpace;
|
||||
overflow: auto;
|
||||
|
||||
.databaseHeader {
|
||||
font-size: 14px;
|
||||
@@ -19,10 +18,6 @@
|
||||
.notebookHeader {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.clickDisabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
271
less/tree.less
@@ -1,270 +1,273 @@
|
||||
@import "./Common/Constants";
|
||||
|
||||
|
||||
.resourceTree {
|
||||
height: 100%;
|
||||
flex: 0 0 auto;
|
||||
.main {
|
||||
height: 100%;
|
||||
}
|
||||
width: 20%;
|
||||
flex: 0 0 auto;
|
||||
.main {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.resourceTreeScroll {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-right: 10px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.userSelectNone {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.treeHovermargin {
|
||||
margin-left: 16px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
padding: @SmallSpace 2px;
|
||||
outline: 0;
|
||||
padding: @SmallSpace 2px;
|
||||
outline: 0;
|
||||
|
||||
&:hover {
|
||||
.hover();
|
||||
}
|
||||
&:hover {
|
||||
.hover();
|
||||
}
|
||||
|
||||
&:active {
|
||||
.active();
|
||||
}
|
||||
&:active {
|
||||
.active();
|
||||
}
|
||||
|
||||
&:focus {
|
||||
.focus();
|
||||
}
|
||||
&:focus {
|
||||
.focus();
|
||||
}
|
||||
}
|
||||
|
||||
.contextmenushowing {
|
||||
background-color: #eee;
|
||||
background-color: #EEE;
|
||||
}
|
||||
|
||||
.collectionstree {
|
||||
width: 100%;
|
||||
margin-top: @DefaultSpace;
|
||||
width: 100%;
|
||||
margin-top: @DefaultSpace;
|
||||
|
||||
.databaseList {
|
||||
list-style-type: none;
|
||||
padding-left: 0px;
|
||||
|
||||
.collectionList {
|
||||
padding-left: (2 * @MediumSpace);
|
||||
.databaseList {
|
||||
list-style-type: none;
|
||||
padding-left: 0px;
|
||||
|
||||
.collectionList {
|
||||
padding-left:(2 * @MediumSpace);
|
||||
}
|
||||
|
||||
.collectionChildList {
|
||||
padding-left: @LargeSpace;
|
||||
}
|
||||
|
||||
.databaseDocuments {
|
||||
padding-left: (5 * @MediumSpace);
|
||||
}
|
||||
}
|
||||
|
||||
.collectionChildList {
|
||||
padding-left: @LargeSpace;
|
||||
}
|
||||
|
||||
.databaseDocuments {
|
||||
padding-left: (5 * @MediumSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pointerCursor {
|
||||
cursor: pointer;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menuEllipsis {
|
||||
padding-right: 6px;
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
position: relative;
|
||||
top: -5px;
|
||||
left: 0px;
|
||||
float: right;
|
||||
display: none;
|
||||
padding-left: 6px !important;
|
||||
line-height: @TreeLineHeight;
|
||||
padding-right: 6px;
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
position: relative;
|
||||
top: -5px;
|
||||
left: 0px;
|
||||
float: right;
|
||||
display: none;
|
||||
padding-left: 6px!important;
|
||||
line-height: @TreeLineHeight;
|
||||
}
|
||||
|
||||
.databaseMenu {
|
||||
.flex-display();
|
||||
.flex-display();
|
||||
}
|
||||
|
||||
.databaseMenu:hover .menuEllipsis,
|
||||
.databaseMenu:focus .menuEllipsis {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.databaseCollChildTextOverflow {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.collectionMenu {
|
||||
.flex-display();
|
||||
.flex-display();
|
||||
}
|
||||
|
||||
.collectionMenu:hover .menuEllipsis,
|
||||
.collectionMenu:focus .menuEllipsis {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.documentsMenu:hover .menuEllipsis,
|
||||
.documentsMenu:focus .menuEllipsis {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.treeChildMenu {
|
||||
display: flex;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.storedProcedureMenu:hover .menuEllipsis,
|
||||
.storedProcedureMenu:focus .menuEllipsis {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.childMenu {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding-left: (6 * @MediumSpace);
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding-left: (6 * @MediumSpace);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.storedChildMenu:hover .menuEllipsis,
|
||||
.storedChildMenu:focus .menuEllipsis {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.contextmenu6 {
|
||||
top: -29px;
|
||||
top: -29px;
|
||||
}
|
||||
|
||||
.userDefinedMenu:hover .contextmenu6 {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.userDefinedchildMenu:hover .menuEllipsis,
|
||||
.userDefinedchildMenu:focus .menuEllipsis {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.triggersMenu:hover .menuEllipsis,
|
||||
.triggersMenu:focus .menuEllipsis {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.triggersChildMenu:hover .menuEllipsis,
|
||||
.triggersChildMenu:focus .menuEllipsis {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.databaseId {
|
||||
font-size: 14px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.storedUdfTriggerMenu {
|
||||
padding-left: 0px;
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
.collectionstree img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: text-top;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
img.collectionsTreeCollapseExpand {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
vertical-align: middle;
|
||||
margin-bottom: 5px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
vertical-align: middle;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.collapsed::before {
|
||||
content: "\23F5";
|
||||
margin-left: 0px;
|
||||
font-size: 15px;
|
||||
content: "\23F5";
|
||||
margin-left: 0px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.expanded::before {
|
||||
content: "\23F7";
|
||||
margin-left: 0px;
|
||||
font-size: 15px;
|
||||
content: '\23F7';
|
||||
margin-left: 0px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.collectionMenuChildren {
|
||||
padding-left: 42px;
|
||||
padding-left: 42px;
|
||||
}
|
||||
|
||||
.main-nav {
|
||||
width: 100vh;
|
||||
height: 40px;
|
||||
background: white;
|
||||
transform-origin: left top;
|
||||
-webkit-transform-origin: left top;
|
||||
-ms-transform-origin: left top;
|
||||
transform: rotate(-90deg) translateX(-100%);
|
||||
-webkit-transform: rotate(-90deg) translateX(-100%);
|
||||
-ms-transform: rotate(-90deg) translateX(-100%);
|
||||
border-bottom: 1px solid #ccc;
|
||||
width: 100vh;
|
||||
height: 40px;
|
||||
background: white;
|
||||
transform-origin: left top;
|
||||
-webkit-transform-origin: left top;
|
||||
-ms-transform-origin: left top;
|
||||
transform: rotate(-90deg) translateX(-100%);
|
||||
-webkit-transform: rotate(-90deg) translateX(-100%);
|
||||
-ms-transform: rotate(-90deg) translateX(-100%);
|
||||
border-bottom: 1px solid #CCC;
|
||||
}
|
||||
|
||||
.main-nav-img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: -32px 0 0 0;
|
||||
transform: rotate(-90deg) translateX(-100%);
|
||||
-webkit-transform: rotate(-90deg) translateX(-100%);
|
||||
-ms-transform: rotate(-90deg) translateX(-100%);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: -32px 0 0 0;
|
||||
transform: rotate(-90deg) translateX(-100%);
|
||||
-webkit-transform: rotate(-90deg) translateX(-100%);
|
||||
-ms-transform: rotate(-90deg) translateX(-100%);
|
||||
}
|
||||
|
||||
.main-nav-img.main-nav-sub-img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0px 0px 0 0;
|
||||
transform: rotate(180deg) translateX(0%);
|
||||
-webkit-transform: rotate(180deg) translateX(0%);
|
||||
-ms-transform: rotate(180deg) translateX(0%);
|
||||
position: absolute;
|
||||
right: -8px;
|
||||
top: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0px 0px 0 0;
|
||||
transform: rotate(180deg) translateX(0%);
|
||||
-webkit-transform: rotate(180deg) translateX(0%);
|
||||
-ms-transform: rotate(180deg) translateX(0%);
|
||||
position: absolute;
|
||||
right: -8px;
|
||||
top: 16px;
|
||||
}
|
||||
|
||||
ul.nav {
|
||||
margin: 0 auto;
|
||||
margin-top: 0px;
|
||||
margin-left: 0px;
|
||||
margin: 0 auto;
|
||||
margin-top: 0px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.mini ul.nav li {
|
||||
float: right;
|
||||
line-height: 25px;
|
||||
height: auto;
|
||||
margin-top: 3px;
|
||||
float: right;
|
||||
line-height: 25px;
|
||||
height: auto;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.spancolchildstyle {
|
||||
padding: 4px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.contextmenubutton {
|
||||
float: right;
|
||||
display: none;
|
||||
float: right;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.highlight:hover > .contextmenubutton {
|
||||
display: unset;
|
||||
.highlight:hover>.contextmenubutton {
|
||||
display: unset;
|
||||
}
|
||||
|
||||
.highlight:hover > .contextmenubutton::after {
|
||||
content: "\2026";
|
||||
font-size: 12px;
|
||||
.highlight:hover>.contextmenubutton::after {
|
||||
content: "\2026";
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.showEllipsis {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
4670
package-lock.json
generated
41
package.json
@@ -42,15 +42,14 @@
|
||||
"@octokit/rest": "17.9.2",
|
||||
"@phosphor/widgets": "1.9.3",
|
||||
"@testing-library/jest-dom": "5.11.9",
|
||||
"@types/lodash": "4.14.171",
|
||||
"@types/mkdirp": "1.0.1",
|
||||
"@types/node-fetch": "2.5.7",
|
||||
"applicationinsights": "1.8.0",
|
||||
"bootstrap": "3.4.1",
|
||||
"canvas": "file:./canvas",
|
||||
"clean-webpack-plugin": "3.0.0",
|
||||
"clean-webpack-plugin": "0.1.19",
|
||||
"clipboard-copy": "4.0.1",
|
||||
"copy-webpack-plugin": "9.0.1",
|
||||
"copy-webpack-plugin": "6.0.2",
|
||||
"crossroads": "0.12.2",
|
||||
"css-element-queries": "1.1.1",
|
||||
"d3": "6.1.1",
|
||||
@@ -81,10 +80,10 @@
|
||||
"plotly.js-cartesian-dist-min": "1.52.3",
|
||||
"post-robot": "10.0.42",
|
||||
"q": "1.5.1",
|
||||
"react": "16.14.0",
|
||||
"react": "16.13.1",
|
||||
"react-animate-height": "2.0.8",
|
||||
"react-dnd": "14.0.2",
|
||||
"react-dnd-html5-backend": "14.0.0",
|
||||
"react-dnd": "9.4.0",
|
||||
"react-dnd-html5-backend": "9.4.0",
|
||||
"react-dom": "16.13.1",
|
||||
"react-hotkeys": "2.0.0",
|
||||
"react-i18next": "11.8.5",
|
||||
@@ -98,7 +97,7 @@
|
||||
"sanitize-html": "2.3.3",
|
||||
"styled-components": "4.3.2",
|
||||
"swr": "0.4.0",
|
||||
"terser-webpack-plugin": "5.1.4",
|
||||
"terser-webpack-plugin": "3.1.0",
|
||||
"underscore": "1.9.1",
|
||||
"utility-types": "3.10.0",
|
||||
"zustand": "3.5.0"
|
||||
@@ -132,7 +131,6 @@
|
||||
"@types/underscore": "1.7.36",
|
||||
"@typescript-eslint/eslint-plugin": "4.22.0",
|
||||
"@typescript-eslint/parser": "4.22.0",
|
||||
"@webpack-cli/serve": "1.5.2",
|
||||
"babel-jest": "24.9.0",
|
||||
"babel-loader": "8.1.0",
|
||||
"buffer": "5.1.0",
|
||||
@@ -154,45 +152,44 @@
|
||||
"html-inline-css-webpack-plugin": "1.11.0",
|
||||
"html-loader": "0.5.5",
|
||||
"html-loader-jest": "0.2.1",
|
||||
"html-webpack-plugin": "5.3.2",
|
||||
"jest": "26.6.3",
|
||||
"jest-canvas-mock": "2.3.1",
|
||||
"html-webpack-plugin": "4.5.2",
|
||||
"jest": "25.5.4",
|
||||
"jest-canvas-mock": "2.1.0",
|
||||
"jest-playwright-preset": "1.5.1",
|
||||
"jest-trx-results-processor": "0.0.7",
|
||||
"less": "3.8.1",
|
||||
"less-loader": "4.1.0",
|
||||
"less-vars-loader": "1.1.0",
|
||||
"mini-css-extract-plugin": "2.1.0",
|
||||
"mini-css-extract-plugin": "0.4.3",
|
||||
"monaco-editor-webpack-plugin": "1.7.0",
|
||||
"node-fetch": "2.6.1",
|
||||
"playwright": "1.13.0",
|
||||
"playwright": "1.10.0",
|
||||
"prettier": "2.2.1",
|
||||
"process": "0.11.10",
|
||||
"raw-loader": "0.5.1",
|
||||
"react-dev-utils": "11.0.4",
|
||||
"rimraf": "3.0.0",
|
||||
"sinon": "3.2.1",
|
||||
"style-loader": "0.23.0",
|
||||
"ts-loader": "9.2.4",
|
||||
"ts-loader": "6.2.2",
|
||||
"tslint": "5.11.0",
|
||||
"tslint-microsoft-contrib": "6.0.0",
|
||||
"typedoc": "0.20.36",
|
||||
"typescript": "4.3.4",
|
||||
"url-loader": "1.1.1",
|
||||
"wait-on": "4.0.2",
|
||||
"webpack": "5.47.0",
|
||||
"webpack-bundle-analyzer": "4.4.2",
|
||||
"webpack-cli": "4.7.2",
|
||||
"webpack-dev-server": "3.11.2"
|
||||
"webpack": "4.46.0",
|
||||
"webpack-bundle-analyzer": "3.6.1",
|
||||
"webpack-cli": "3.3.10",
|
||||
"webpack-dev-server": "3.11.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "webpack serve --mode development",
|
||||
"start": "node --max-old-space-size=10196 node_modules/webpack-dev-server/bin/webpack-dev-server.js",
|
||||
"dev": "echo \"WARNING: npm run dev has been deprecated\" && npm run build",
|
||||
"build:dataExplorer:ci": "npm run build:ci",
|
||||
"build": "npm run format:check && npm run lint && npm run compile && npm run compile:strict && npm run pack:prod && npm run copyToConsumers",
|
||||
"build:ci": "npm run format:check && npm run lint && npm run compile && npm run compile:strict && npm run pack:fast",
|
||||
"pack:prod": "webpack --mode production",
|
||||
"pack:fast": "webpack --mode development --progress",
|
||||
"pack:prod": "node --max_old_space_size=10196 ./node_modules/webpack/bin/webpack.js --mode production",
|
||||
"pack:fast": "node --max_old_space_size=10196 ./node_modules/webpack/bin/webpack.js --mode development --progress",
|
||||
"copyToConsumers": "node copyToConsumers",
|
||||
"test": "rimraf coverage && jest",
|
||||
"test:e2e": "jest -c ./jest.config.playwright.js --detectOpenHandles",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
|
||||
import { userContext } from "../UserContext";
|
||||
import { NormalizedEventKey } from "./Constants";
|
||||
|
||||
export interface CollapsedResourceTreeProps {
|
||||
toggleLeftPaneExpanded: () => void;
|
||||
@@ -12,21 +11,6 @@ export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps
|
||||
toggleLeftPaneExpanded,
|
||||
isLeftPaneExpanded,
|
||||
}: CollapsedResourceTreeProps): JSX.Element => {
|
||||
const focusButton = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>;
|
||||
|
||||
useEffect(() => {
|
||||
if (focusButton.current) {
|
||||
focusButton.current.focus();
|
||||
}
|
||||
});
|
||||
|
||||
const onKeyPressToggleLeftPaneExpanded = (event: React.KeyboardEvent) => {
|
||||
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
|
||||
toggleLeftPaneExpanded();
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div id="mini" className={!isLeftPaneExpanded ? "mini toggle-mini" : "hiddenMain"}>
|
||||
<div className="main-nav nav">
|
||||
@@ -37,14 +21,11 @@ export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label="Expand Tree"
|
||||
onClick={toggleLeftPaneExpanded}
|
||||
onKeyPress={onKeyPressToggleLeftPaneExpanded}
|
||||
ref={focusButton}
|
||||
>
|
||||
<span className="leftarrowCollapsed">
|
||||
<span className="leftarrowCollapsed" onClick={toggleLeftPaneExpanded}>
|
||||
<img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" />
|
||||
</span>
|
||||
<span className="collectionCollapsed">
|
||||
<span className="collectionCollapsed" onClick={toggleLeftPaneExpanded}>
|
||||
<span>{userContext.apiType} API</span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
@@ -94,9 +94,7 @@ export class Flights {
|
||||
public static readonly MongoIndexEditor = "mongoindexeditor";
|
||||
public static readonly MongoIndexing = "mongoindexing";
|
||||
public static readonly AutoscaleTest = "autoscaletest";
|
||||
public static readonly PartitionKeyTest = "partitionkeytest";
|
||||
public static readonly PKPartitionKeyTest = "pkpartitionkeytest";
|
||||
public static readonly Phoenix = "phoenix";
|
||||
public static readonly SchemaAnalyzer = "schemaanalyzer";
|
||||
}
|
||||
|
||||
export class AfecFeatures {
|
||||
@@ -338,14 +336,6 @@ export enum ConflictOperationType {
|
||||
Delete = "delete",
|
||||
}
|
||||
|
||||
export enum ConnectionStatusType {
|
||||
Connect = "Connect",
|
||||
Connecting = "Connecting",
|
||||
Connected = "Connected",
|
||||
Failed = "Connection Failed",
|
||||
ReConnect = "Reconnect",
|
||||
}
|
||||
|
||||
export const EmulatorMasterKey =
|
||||
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
|
||||
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
|
||||
@@ -355,32 +345,10 @@ export const StyleConstants = require("less-vars-loader!../../less/Common/Consta
|
||||
|
||||
export class Notebook {
|
||||
public static readonly defaultBasePath = "./notebooks";
|
||||
public static readonly heartbeatDelayMs = 60000;
|
||||
public static readonly heartbeatDelayMs = 5000;
|
||||
public static readonly kernelRestartInitialDelayMs = 1000;
|
||||
public static readonly kernelRestartMaxDelayMs = 20000;
|
||||
public static readonly autoSaveIntervalMs = 120000;
|
||||
public static readonly memoryGuageToGB = 1048576;
|
||||
public static readonly temporarilyDownMsg = "Notebooks is currently not available. We are working on it.";
|
||||
public static readonly mongoShellTemporarilyDownMsg =
|
||||
"We have identified an issue with the Mongo Shell and it is unavailable right now. We are actively working on the mitigation.";
|
||||
public static readonly cassandraShellTemporarilyDownMsg =
|
||||
"We have identified an issue with the Cassandra Shell and it is unavailable right now. We are actively working on the mitigation.";
|
||||
public static saveNotebookModalTitle = "Save Notebook in temporary workspace";
|
||||
public static saveNotebookModalContent =
|
||||
"This notebook will be saved in the temporary workspace and will be removed when the session expires. To save your work permanently, save your notebooks to a GitHub repository or download the notebooks to your local machine before the session ends.";
|
||||
public static newNotebookModalTitle = "Create Notebook in temporary workspace";
|
||||
public static newNotebookUploadModalTitle = "Upload Notebook in temporary workspace";
|
||||
public static newNotebookModalContent1 =
|
||||
"A temporary workspace will be created to enable you to work with notebooks. When the session expires, any notebooks in the workspace will be removed.";
|
||||
public static newNotebookModalContent2 =
|
||||
"To save your work permanently, save your notebooks to a GitHub repository or download the notebooks to your local machine before the session ends. ";
|
||||
public static galleryNotebookDownloadContent1 =
|
||||
"To download, run, and make changes to this sample notebook, a temporary workspace will be created. When the session expires, any notebooks in the workspace will be removed.";
|
||||
public static galleryNotebookDownloadContent2 =
|
||||
"To save your work permanently, save your notebooks to a GitHub repository or download the Notebooks to your local machine before the session ends. ";
|
||||
public static cosmosNotebookHomePageUrl = "https://aka.ms/cosmos-notebooks-limits";
|
||||
public static cosmosNotebookGitDocumentationUrl = "https://aka.ms/cosmos-notebooks-github";
|
||||
public static learnMore = "Learn more.";
|
||||
}
|
||||
|
||||
export class SparkLibrary {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as ExplorerSettings from "../Shared/ExplorerSettings";
|
||||
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
||||
import * as HeadersUtility from "./HeadersUtility";
|
||||
import { ExplorerSettings } from "../Shared/ExplorerSettings";
|
||||
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
||||
|
||||
describe("Headers Utility", () => {
|
||||
describe("shouldEnableCrossPartitionKeyForResourceWithPartitionKey()", () => {
|
||||
|
||||
@@ -3,16 +3,8 @@ import { resetConfigContext, updateConfigContext } from "../ConfigContext";
|
||||
import { DatabaseAccount } from "../Contracts/DataModels";
|
||||
import { Collection } from "../Contracts/ViewModels";
|
||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||
import { extractFeatures } from "../Platform/Hosted/extractFeatures";
|
||||
import { updateUserContext } from "../UserContext";
|
||||
import {
|
||||
deleteDocument,
|
||||
getEndpoint,
|
||||
getFeatureEndpointOrDefault,
|
||||
queryDocuments,
|
||||
readDocument,
|
||||
updateDocument,
|
||||
} from "./MongoProxyClient";
|
||||
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
|
||||
|
||||
const databaseId = "testDB";
|
||||
|
||||
@@ -254,31 +246,4 @@ describe("MongoProxyClient", () => {
|
||||
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
|
||||
});
|
||||
});
|
||||
describe("getFeatureEndpointOrDefault", () => {
|
||||
beforeEach(() => {
|
||||
resetConfigContext();
|
||||
updateConfigContext({
|
||||
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
||||
});
|
||||
const params = new URLSearchParams({
|
||||
"feature.mongoProxyEndpoint": "https://localhost:12901",
|
||||
"feature.mongoProxyAPIs": "readDocument|createDocument",
|
||||
});
|
||||
const features = extractFeatures(params);
|
||||
updateUserContext({
|
||||
authType: AuthType.AAD,
|
||||
features: features,
|
||||
});
|
||||
});
|
||||
|
||||
it("returns a local endpoint", () => {
|
||||
const endpoint = getFeatureEndpointOrDefault("readDocument");
|
||||
expect(endpoint).toEqual("https://localhost:12901/api/mongo/explorer");
|
||||
});
|
||||
|
||||
it("returns a production endpoint", () => {
|
||||
const endpoint = getFeatureEndpointOrDefault("deleteDocument");
|
||||
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/mongo/explorer");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,6 @@ import * as DataModels from "../Contracts/DataModels";
|
||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||
import { Collection } from "../Contracts/ViewModels";
|
||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||
import { hasFlag } from "../Platform/Hosted/extractFeatures";
|
||||
import { userContext } from "../UserContext";
|
||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
||||
@@ -79,7 +78,7 @@ export function queryDocuments(
|
||||
: "",
|
||||
};
|
||||
|
||||
const endpoint = getFeatureEndpointOrDefault("resourcelist") || "";
|
||||
const endpoint = getEndpoint() || "";
|
||||
|
||||
const headers = {
|
||||
...defaultHeaders,
|
||||
@@ -142,8 +141,7 @@ export function readDocument(
|
||||
: "",
|
||||
};
|
||||
|
||||
const endpoint = getFeatureEndpointOrDefault("readDocument");
|
||||
|
||||
const endpoint = getEndpoint();
|
||||
return window
|
||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||
method: "GET",
|
||||
@@ -183,7 +181,7 @@ export function createDocument(
|
||||
pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "",
|
||||
};
|
||||
|
||||
const endpoint = getFeatureEndpointOrDefault("createDocument");
|
||||
const endpoint = getEndpoint();
|
||||
|
||||
return window
|
||||
.fetch(`${endpoint}/resourcelist?${queryString.stringify(params)}`, {
|
||||
@@ -227,7 +225,7 @@ export function updateDocument(
|
||||
? documentId.partitionKeyProperty
|
||||
: "",
|
||||
};
|
||||
const endpoint = getFeatureEndpointOrDefault("updateDocument");
|
||||
const endpoint = getEndpoint();
|
||||
|
||||
return window
|
||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||
@@ -268,7 +266,7 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
|
||||
? documentId.partitionKeyProperty
|
||||
: "",
|
||||
};
|
||||
const endpoint = getFeatureEndpointOrDefault("deleteDocument");
|
||||
const endpoint = getEndpoint();
|
||||
|
||||
return window
|
||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||
@@ -311,7 +309,7 @@ export function createMongoCollectionWithProxy(
|
||||
autoPilotThroughput: params.autoPilotMaxThroughput?.toString(),
|
||||
};
|
||||
|
||||
const endpoint = getFeatureEndpointOrDefault("createCollectionWithProxy");
|
||||
const endpoint = getEndpoint();
|
||||
|
||||
return window
|
||||
.fetch(
|
||||
@@ -335,15 +333,8 @@ export function createMongoCollectionWithProxy(
|
||||
});
|
||||
}
|
||||
|
||||
export function getFeatureEndpointOrDefault(feature: string): string {
|
||||
return hasFlag(userContext.features.mongoProxyAPIs, feature)
|
||||
? getEndpoint(userContext.features.mongoProxyEndpoint)
|
||||
: getEndpoint();
|
||||
}
|
||||
|
||||
export function getEndpoint(customEndpoint?: string): string {
|
||||
let url = customEndpoint ? customEndpoint : configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT;
|
||||
url += "/api/mongo/explorer";
|
||||
export function getEndpoint(): string {
|
||||
let url = (configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT) + "/api/mongo/explorer";
|
||||
|
||||
if (userContext.authType === AuthType.EncryptedToken) {
|
||||
url = url.replace("api/mongo", "api/guest/mongo");
|
||||
|
||||
@@ -1,40 +1,18 @@
|
||||
import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
|
||||
import refreshImg from "../../images/refresh-cosmos.svg";
|
||||
import { AuthType } from "../AuthType";
|
||||
import Explorer from "../Explorer/Explorer";
|
||||
import { ResourceTokenTree } from "../Explorer/Tree/ResourceTokenTree";
|
||||
import { ResourceTree } from "../Explorer/Tree/ResourceTree";
|
||||
import { userContext } from "../UserContext";
|
||||
import { NormalizedEventKey } from "./Constants";
|
||||
|
||||
export interface ResourceTreeContainerProps {
|
||||
export interface ResourceTreeProps {
|
||||
toggleLeftPaneExpanded: () => void;
|
||||
isLeftPaneExpanded: boolean;
|
||||
container: Explorer;
|
||||
}
|
||||
|
||||
export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps> = ({
|
||||
export const ResourceTree: FunctionComponent<ResourceTreeProps> = ({
|
||||
toggleLeftPaneExpanded,
|
||||
isLeftPaneExpanded,
|
||||
container,
|
||||
}: ResourceTreeContainerProps): JSX.Element => {
|
||||
const focusButton = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>;
|
||||
|
||||
useEffect(() => {
|
||||
if (isLeftPaneExpanded) {
|
||||
if (focusButton.current) {
|
||||
focusButton.current.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const onKeyPressToggleLeftPaneExpanded = (event: React.KeyboardEvent) => {
|
||||
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
|
||||
toggleLeftPaneExpanded();
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
}: ResourceTreeProps): JSX.Element => {
|
||||
return (
|
||||
<div id="main" className={isLeftPaneExpanded ? "main" : "hiddenMain"}>
|
||||
{/* Collections Window - - Start */}
|
||||
@@ -60,11 +38,9 @@ export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps
|
||||
id="expandToggleLeftPaneButton"
|
||||
role="button"
|
||||
onClick={toggleLeftPaneExpanded}
|
||||
onKeyPress={onKeyPressToggleLeftPaneExpanded}
|
||||
tabIndex={0}
|
||||
aria-label="Collapse Tree"
|
||||
title="Collapse Tree"
|
||||
ref={focusButton}
|
||||
>
|
||||
<img className="refreshcol1" src={arrowLeftImg} alt="Hide" />
|
||||
</span>
|
||||
@@ -72,11 +48,9 @@ export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps
|
||||
</div>
|
||||
</div>
|
||||
{userContext.authType === AuthType.ResourceToken ? (
|
||||
<ResourceTokenTree />
|
||||
) : userContext.features.enableKoResourceTree ? (
|
||||
<div style={{ overflowY: "auto" }} data-bind="react:resourceTree" />
|
||||
<div style={{ overflowY: "auto" }} data-bind="react:resourceTreeForResourceToken" />
|
||||
) : (
|
||||
<ResourceTree container={container} />
|
||||
<div style={{ overflowY: "auto" }} data-bind="react:resourceTree" />
|
||||
)}
|
||||
</div>
|
||||
{/* Collections Window - End */}
|
||||
@@ -9,7 +9,7 @@ export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children }:
|
||||
return (
|
||||
<span>
|
||||
<TooltipHost content={children}>
|
||||
<Icon iconName="Info" ariaLabel="Info" className="panelInfoIcon" tabIndex={0} />
|
||||
<Icon iconName="Info" ariaLabel="Info" className="panelInfoIcon" />
|
||||
</TooltipHost>
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
jest.mock("../../Utils/arm/request");
|
||||
jest.mock("../CosmosClient");
|
||||
import ko from "knockout";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels";
|
||||
import { Database } from "../../Contracts/ViewModels";
|
||||
import { useDatabases } from "../../Explorer/useDatabases";
|
||||
import { updateUserContext } from "../../UserContext";
|
||||
import { armRequest } from "../../Utils/arm/request";
|
||||
import { client } from "../CosmosClient";
|
||||
@@ -26,15 +23,6 @@ describe("createCollection", () => {
|
||||
} as DatabaseAccount,
|
||||
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 () => {
|
||||
|
||||
@@ -4,16 +4,20 @@ import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/Contai
|
||||
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { useDatabases } from "../../Explorer/useDatabases";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { getCollectionName } from "../../Utils/APITypeUtils";
|
||||
import { createUpdateCassandraTable } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
||||
import { createUpdateGremlinGraph } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
||||
import { createUpdateMongoDBCollection } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
|
||||
import { createUpdateSqlContainer } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
|
||||
import { createUpdateTable } from "../../Utils/arm/generatedClients/cosmos/tableResources";
|
||||
import {
|
||||
createUpdateCassandraTable,
|
||||
getCassandraTable,
|
||||
} from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
||||
import { createUpdateGremlinGraph, getGremlinGraph } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
||||
import {
|
||||
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 { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
@@ -55,16 +59,6 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
|
||||
};
|
||||
|
||||
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;
|
||||
switch (apiType) {
|
||||
case "SQL":
|
||||
@@ -83,6 +77,23 @@ const createCollectionWithARM = async (params: DataModels.CreateCollectionParams
|
||||
};
|
||||
|
||||
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 resource: ARMTypes.SqlContainerResource = {
|
||||
id: params.collectionId,
|
||||
@@ -120,6 +131,23 @@ const createSqlContainer = async (params: DataModels.CreateCollectionParams): Pr
|
||||
|
||||
const createMongoCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
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 resource: ARMTypes.MongoDBCollectionResource = {
|
||||
id: params.collectionId,
|
||||
@@ -161,6 +189,23 @@ const createMongoCollection = async (params: DataModels.CreateCollectionParams):
|
||||
};
|
||||
|
||||
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 resource: ARMTypes.CassandraTableResource = {
|
||||
id: params.collectionId,
|
||||
@@ -188,6 +233,23 @@ const createCassandraTable = async (params: DataModels.CreateCollectionParams):
|
||||
};
|
||||
|
||||
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 resource: ARMTypes.GremlinGraphResource = {
|
||||
id: params.collectionId,
|
||||
@@ -222,6 +284,22 @@ const createGraph = async (params: DataModels.CreateCollectionParams): Promise<D
|
||||
};
|
||||
|
||||
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 resource: ARMTypes.TableResource = {
|
||||
id: params.collectionId,
|
||||
|
||||
@@ -2,13 +2,20 @@ import { DatabaseResponse } from "@azure/cosmos";
|
||||
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { useDatabases } from "../../Explorer/useDatabases";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { getDatabaseName } from "../../Utils/APITypeUtils";
|
||||
import { createUpdateCassandraKeyspace } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
||||
import { createUpdateGremlinDatabase } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
||||
import { createUpdateMongoDBDatabase } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
|
||||
import { createUpdateSqlDatabase } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
|
||||
import {
|
||||
createUpdateCassandraKeyspace,
|
||||
getCassandraKeyspace,
|
||||
} from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
||||
import {
|
||||
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 {
|
||||
CassandraKeyspaceCreateUpdateParameters,
|
||||
CreateUpdateOptions,
|
||||
@@ -41,11 +48,6 @@ export async function createDatabase(params: DataModels.CreateDatabaseParams): P
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
switch (apiType) {
|
||||
@@ -63,6 +65,22 @@ async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): P
|
||||
}
|
||||
|
||||
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 rpPayload: SqlDatabaseCreateUpdateParameters = {
|
||||
properties: {
|
||||
@@ -83,6 +101,22 @@ async function createSqlDatabase(params: DataModels.CreateDatabaseParams): Promi
|
||||
}
|
||||
|
||||
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 rpPayload: MongoDBDatabaseCreateUpdateParameters = {
|
||||
properties: {
|
||||
@@ -103,6 +137,22 @@ async function createMongoDatabase(params: DataModels.CreateDatabaseParams): Pro
|
||||
}
|
||||
|
||||
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 rpPayload: CassandraKeyspaceCreateUpdateParameters = {
|
||||
properties: {
|
||||
@@ -123,6 +173,22 @@ async function createCassandraKeyspace(params: DataModels.CreateDatabaseParams):
|
||||
}
|
||||
|
||||
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 rpPayload: GremlinDatabaseCreateUpdateParameters = {
|
||||
properties: {
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface ConfigContext {
|
||||
hostedExplorerURL: string;
|
||||
armAPIVersion?: string;
|
||||
allowedJunoOrigins: string[];
|
||||
enableSchemaAnalyzer: boolean;
|
||||
msalRedirectURI?: string;
|
||||
}
|
||||
|
||||
@@ -62,6 +63,7 @@ let configContext: Readonly<ConfigContext> = {
|
||||
"https://tools-staging.cosmos.azure.com",
|
||||
"https://localhost",
|
||||
],
|
||||
enableSchemaAnalyzer: false,
|
||||
};
|
||||
|
||||
export function resetConfigContext(): void {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { ConnectionStatusType } from "../Common/Constants";
|
||||
|
||||
export interface DatabaseAccount {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -11,7 +9,6 @@ export interface DatabaseAccount {
|
||||
|
||||
export interface DatabaseAccountExtendedProperties {
|
||||
documentEndpoint?: string;
|
||||
disableLocalAuth?: boolean;
|
||||
tableEndpoint?: string;
|
||||
gremlinEndpoint?: string;
|
||||
cassandraEndpoint?: string;
|
||||
@@ -498,8 +495,3 @@ export interface MemoryUsageInfo {
|
||||
freeKB: number;
|
||||
totalKB: number;
|
||||
}
|
||||
|
||||
export interface ContainerConnectionInfo {
|
||||
status: ConnectionStatusType;
|
||||
//need to add ram and rom info
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
Resource,
|
||||
StoredProcedureDefinition,
|
||||
TriggerDefinition,
|
||||
UserDefinedFunctionDefinition,
|
||||
UserDefinedFunctionDefinition
|
||||
} from "@azure/cosmos";
|
||||
import Explorer from "../Explorer/Explorer";
|
||||
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/ConsoleData";
|
||||
@@ -370,6 +370,7 @@ export enum TerminalKind {
|
||||
Default = 0,
|
||||
Mongo = 1,
|
||||
Cassandra = 2,
|
||||
PostgreSQL = 3
|
||||
}
|
||||
|
||||
export interface DataExplorerInputsFrame {
|
||||
|
||||
@@ -22,8 +22,8 @@ describe("The Heatmap Control", () => {
|
||||
};
|
||||
|
||||
let heatmap: Heatmap;
|
||||
const theme: PortalTheme = 1;
|
||||
const divElement = `<div id="${Heatmap.elementId}"></div>`;
|
||||
let theme: PortalTheme = 1;
|
||||
const divElement: string = `<div id="${Heatmap.elementId}"></div>`;
|
||||
|
||||
describe("drawHeatmap rendering", () => {
|
||||
beforeEach(() => {
|
||||
@@ -100,7 +100,7 @@ describe("iframe rendering when there is no data", () => {
|
||||
});
|
||||
|
||||
it("should show a no data message with a dark theme", () => {
|
||||
const data = {
|
||||
let data = {
|
||||
data: {
|
||||
signature: "pcIframe",
|
||||
data: {
|
||||
@@ -111,7 +111,7 @@ describe("iframe rendering when there is no data", () => {
|
||||
},
|
||||
};
|
||||
|
||||
const divElement = `<div id="${Heatmap.elementId}"></div>`;
|
||||
const divElement: string = `<div id="${Heatmap.elementId}"></div>`;
|
||||
document.body.innerHTML = divElement;
|
||||
|
||||
handleMessage(data as MessageEvent);
|
||||
@@ -120,7 +120,7 @@ describe("iframe rendering when there is no data", () => {
|
||||
});
|
||||
|
||||
it("should show a no data message with a white theme", () => {
|
||||
const data = {
|
||||
let data = {
|
||||
data: {
|
||||
signature: "pcIframe",
|
||||
data: {
|
||||
@@ -131,7 +131,7 @@ describe("iframe rendering when there is no data", () => {
|
||||
},
|
||||
};
|
||||
|
||||
const divElement = `<div id="${Heatmap.elementId}"></div>`;
|
||||
const divElement: string = `<div id="${Heatmap.elementId}"></div>`;
|
||||
document.body.innerHTML = divElement;
|
||||
|
||||
handleMessage(data as MessageEvent);
|
||||
|
||||
@@ -39,7 +39,7 @@ export class Heatmap {
|
||||
}
|
||||
}
|
||||
|
||||
private _getFontStyles(size: number = StyleConstants.MediumFontSize, color = "#838383"): FontSettings {
|
||||
private _getFontStyles(size: number = StyleConstants.MediumFontSize, color: string = "#838383"): FontSettings {
|
||||
return {
|
||||
family: StyleConstants.DataExplorerFont,
|
||||
size,
|
||||
@@ -78,9 +78,9 @@ export class Heatmap {
|
||||
// go thru all rows and create 2d matrix for heatmap...
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
output.yAxisPoints.push(rows[i]);
|
||||
const dataPoints: number[] = [];
|
||||
let dataPoints: number[] = [];
|
||||
for (let a = 0; a < output.xAxisPoints.length; a++) {
|
||||
const row: PartitionTimeStampToData = data[rows[i]];
|
||||
let row: PartitionTimeStampToData = data[rows[i]];
|
||||
dataPoints.push(row[output.xAxisPoints[a]]["Normalized Throughput"]);
|
||||
}
|
||||
output.dataPoints.push(dataPoints);
|
||||
@@ -193,7 +193,7 @@ export class Heatmap {
|
||||
this._getLayoutSettings(),
|
||||
this._getChartDisplaySettings()
|
||||
);
|
||||
const plotDiv: any = document.getElementById(Heatmap.elementId);
|
||||
let plotDiv: any = document.getElementById(Heatmap.elementId);
|
||||
plotDiv.on("plotly_click", (data: any) => {
|
||||
let timeSelected: string = data.points[0].x;
|
||||
timeSelected = timeSelected.replace(" ", "T");
|
||||
@@ -205,7 +205,7 @@ export class Heatmap {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const output = [];
|
||||
let output = [];
|
||||
for (let i = 0; i < this._chartData.dataPoints.length; i++) {
|
||||
output.push(this._chartData.dataPoints[i][xAxisIndex]);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import { userContext } from "../UserContext";
|
||||
import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils";
|
||||
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
|
||||
import Explorer from "./Explorer";
|
||||
import { useNotebook } from "./Notebook/useNotebook";
|
||||
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
|
||||
import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel";
|
||||
import StoredProcedure from "./Tree/StoredProcedure";
|
||||
@@ -50,10 +49,7 @@ export const createDatabaseContextMenu = (container: Explorer, databaseId: strin
|
||||
onClick: () =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"Delete " + getDatabaseName(),
|
||||
<DeleteDatabaseConfirmationPanel refreshDatabases={() => container.refreshAllDatabases()} />
|
||||
),
|
||||
.openSidePanel("Delete " + getDatabaseName(), <DeleteDatabaseConfirmationPanel explorer={container} />),
|
||||
label: `Delete ${getDatabaseName()}`,
|
||||
styleClass: "deleteDatabaseMenuItem",
|
||||
});
|
||||
@@ -83,16 +79,15 @@ export const createCollectionContextMenuButton = (
|
||||
|
||||
items.push({
|
||||
iconSrc: HostedTerminalIcon,
|
||||
isDisabled: useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown,
|
||||
onClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||
if (useNotebook.getState().isShellEnabled) {
|
||||
if (container.isShellEnabled()) {
|
||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||
} else {
|
||||
selectedCollection && selectedCollection.onNewMongoShellClick();
|
||||
}
|
||||
},
|
||||
label: useNotebook.getState().isShellEnabled ? "Open Mongo Shell" : "New Shell",
|
||||
label: container.isShellEnabled() ? "Open Mongo Shell" : "New Shell",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -110,7 +105,7 @@ export const createCollectionContextMenuButton = (
|
||||
iconSrc: AddUdfIcon,
|
||||
onClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection);
|
||||
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, undefined);
|
||||
},
|
||||
label: "New UDF",
|
||||
});
|
||||
@@ -130,10 +125,7 @@ export const createCollectionContextMenuButton = (
|
||||
onClick: () =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"Delete " + getCollectionName(),
|
||||
<DeleteCollectionConfirmationPane refreshDatabases={() => container.refreshAllDatabases()} />
|
||||
),
|
||||
.openSidePanel("Delete " + getCollectionName(), <DeleteCollectionConfirmationPane explorer={container} />),
|
||||
label: `Delete ${getCollectionName()}`,
|
||||
styleClass: "deleteCollectionMenuItem",
|
||||
});
|
||||
|
||||
@@ -8,9 +8,7 @@ import TriangleDownIcon from "../../../../images/Triangle-down.svg";
|
||||
import TriangleRightIcon from "../../../../images/Triangle-right.svg";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
|
||||
export interface AccordionComponentProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
export interface AccordionComponentProps {}
|
||||
|
||||
export class AccordionComponent extends React.Component<AccordionComponentProps> {
|
||||
public render(): JSX.Element {
|
||||
@@ -80,7 +78,7 @@ export class AccordionItemComponent extends React.Component<AccordionItemCompone
|
||||
);
|
||||
}
|
||||
|
||||
private onHeaderClick = (): void => {
|
||||
private onHeaderClick = (_event: React.MouseEvent<HTMLDivElement>): void => {
|
||||
this.setState({ isExpanded: !this.state.isExpanded });
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Icon, Label, Stack } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import { NormalizedEventKey } from "../../../Common/Constants";
|
||||
import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
|
||||
|
||||
export interface CollapsibleSectionProps {
|
||||
@@ -31,13 +30,6 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
|
||||
}
|
||||
}
|
||||
|
||||
private onKeyPress = (event: React.KeyboardEvent) => {
|
||||
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
|
||||
this.toggleCollapsed();
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
@@ -47,11 +39,6 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
|
||||
verticalAlign="center"
|
||||
tokens={accordionStackTokens}
|
||||
onClick={this.toggleCollapsed}
|
||||
onKeyPress={this.onKeyPress}
|
||||
tabIndex={0}
|
||||
aria-name="Advanced"
|
||||
role="button"
|
||||
aria-expanded={this.state.isExpanded}
|
||||
>
|
||||
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} />
|
||||
<Label>{this.props.title}</Label>
|
||||
|
||||
@@ -3,14 +3,9 @@
|
||||
exports[`CollapsibleSectionComponent renders 1`] = `
|
||||
<Fragment>
|
||||
<Stack
|
||||
aria-expanded={true}
|
||||
aria-name="Advanced"
|
||||
className="collapsibleSection"
|
||||
horizontal={true}
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
|
||||
@@ -121,7 +121,8 @@ export class CommandButtonComponent extends React.Component<CommandButtonCompone
|
||||
if (!this.dropdownElt || !this.expandButtonElt) {
|
||||
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 {
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
Link,
|
||||
PrimaryButton,
|
||||
ProgressIndicator,
|
||||
Text,
|
||||
TextField,
|
||||
} from "@fluentui/react";
|
||||
import React, { FC } from "react";
|
||||
@@ -24,78 +23,13 @@ export interface DialogState {
|
||||
dialogProps?: DialogProps;
|
||||
openDialog: (props: DialogProps) => void;
|
||||
closeDialog: () => void;
|
||||
showOkCancelModalDialog: (
|
||||
title: string,
|
||||
subText: string,
|
||||
okLabel: string,
|
||||
onOk: () => void,
|
||||
cancelLabel: string,
|
||||
onCancel: () => void,
|
||||
contentHtml?: JSX.Element,
|
||||
choiceGroupProps?: IChoiceGroupProps,
|
||||
textFieldProps?: TextFieldProps,
|
||||
primaryButtonDisabled?: boolean
|
||||
) => void;
|
||||
showOkModalDialog: (title: string, subText: string) => void;
|
||||
}
|
||||
|
||||
export const useDialog: UseStore<DialogState> = create((set, get) => ({
|
||||
export const useDialog: UseStore<DialogState> = create((set) => ({
|
||||
visible: false,
|
||||
openDialog: (props: DialogProps) => set(() => ({ visible: true, dialogProps: props })),
|
||||
closeDialog: () =>
|
||||
set(
|
||||
(state) => ({
|
||||
visible: false,
|
||||
openDialog: state.openDialog,
|
||||
closeDialog: state.closeDialog,
|
||||
showOkCancelModalDialog: state.showOkCancelModalDialog,
|
||||
showOkModalDialog: state.showOkModalDialog,
|
||||
}),
|
||||
true // TODO: This probably should not be true but its causing a prod bug so easier to just set the proper state above
|
||||
),
|
||||
showOkCancelModalDialog: (
|
||||
title: string,
|
||||
subText: string,
|
||||
okLabel: string,
|
||||
onOk: () => void,
|
||||
cancelLabel: string,
|
||||
onCancel: () => void,
|
||||
contentHtml?: JSX.Element,
|
||||
choiceGroupProps?: IChoiceGroupProps,
|
||||
textFieldProps?: TextFieldProps,
|
||||
primaryButtonDisabled?: boolean
|
||||
): void =>
|
||||
get().openDialog({
|
||||
isModal: true,
|
||||
title,
|
||||
subText,
|
||||
primaryButtonText: okLabel,
|
||||
secondaryButtonText: cancelLabel,
|
||||
onPrimaryButtonClick: () => {
|
||||
get().closeDialog();
|
||||
onOk && onOk();
|
||||
},
|
||||
onSecondaryButtonClick: () => {
|
||||
get().closeDialog();
|
||||
onCancel && onCancel();
|
||||
},
|
||||
contentHtml,
|
||||
choiceGroupProps,
|
||||
textFieldProps,
|
||||
primaryButtonDisabled,
|
||||
}),
|
||||
showOkModalDialog: (title: string, subText: string): void =>
|
||||
get().openDialog({
|
||||
isModal: true,
|
||||
title,
|
||||
subText,
|
||||
primaryButtonText: "Close",
|
||||
secondaryButtonText: undefined,
|
||||
onPrimaryButtonClick: () => {
|
||||
get().closeDialog();
|
||||
},
|
||||
onSecondaryButtonClick: undefined,
|
||||
}),
|
||||
set((state) => ({ visible: false, openDialog: state.openDialog, closeDialog: state.closeDialog }), true),
|
||||
}));
|
||||
|
||||
export interface TextFieldProps extends ITextFieldProps {
|
||||
@@ -128,7 +62,6 @@ export interface DialogProps {
|
||||
type?: DialogType;
|
||||
showCloseButton?: boolean;
|
||||
onDismiss?: () => void;
|
||||
contentHtml?: JSX.Element;
|
||||
}
|
||||
|
||||
const DIALOG_MIN_WIDTH = "400px";
|
||||
@@ -155,7 +88,6 @@ export const Dialog: FC = () => {
|
||||
type,
|
||||
showCloseButton,
|
||||
onDismiss,
|
||||
contentHtml,
|
||||
} = props || {};
|
||||
|
||||
const dialogProps: IDialogProps = {
|
||||
@@ -187,7 +119,8 @@ export const Dialog: FC = () => {
|
||||
text: secondaryButtonText,
|
||||
onClick: onSecondaryButtonClick,
|
||||
}
|
||||
: undefined;
|
||||
: {};
|
||||
|
||||
return visible ? (
|
||||
<FluentDialog {...dialogProps}>
|
||||
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
|
||||
@@ -197,7 +130,6 @@ export const Dialog: FC = () => {
|
||||
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
|
||||
</Link>
|
||||
)}
|
||||
{contentHtml && <Text>{contentHtml}</Text>}
|
||||
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
|
||||
<DialogFooter>
|
||||
<PrimaryButton {...primaryButtonProps} />
|
||||
|
||||
@@ -56,7 +56,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
||||
this.editor = editor;
|
||||
const queryEditorModel = this.editor.getModel();
|
||||
if (!this.props.isReadOnly && this.props.onContentChanged) {
|
||||
queryEditorModel.onDidChangeContent(() => {
|
||||
queryEditorModel.onDidChangeContent((e: monaco.editor.IModelContentChangedEvent) => {
|
||||
const queryEditorModel = this.editor.getModel();
|
||||
this.props.onContentChanged(queryEditorModel.getValue());
|
||||
});
|
||||
|
||||
@@ -56,7 +56,7 @@ export class GitHubReposComponent extends React.Component<GitHubReposComponentPr
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>{content}</div>
|
||||
<div className={"paneMainContent"}>{content}</div>
|
||||
{!this.props.showAuthorizeAccess && (
|
||||
<>
|
||||
<div className={"paneFooter"} style={ContentFooterStyle}>
|
||||
|
||||
@@ -5,12 +5,6 @@
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
|
||||
.input-type-head-text-field {
|
||||
width: 100%;
|
||||
}
|
||||
.input-query-form {
|
||||
width: 100%;
|
||||
}
|
||||
textarea {
|
||||
width: 100%;
|
||||
line-height: 1;
|
||||
@@ -27,11 +21,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.input-typeahead-chocies-container {
|
||||
border: 1px solid lightgrey;
|
||||
padding: 5px 10px 5px 10px;
|
||||
cursor: pointer;
|
||||
.choice-caption{
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
* typeaheadOverrideOptions: { dynamic:false }
|
||||
*
|
||||
*/
|
||||
import { getTheme, IconButton, IIconProps, List, Stack, TextField } from "@fluentui/react";
|
||||
import "jquery-typeahead";
|
||||
import * as React from "react";
|
||||
import { KeyCodes } from "../../../Common/Constants";
|
||||
import "./InputTypeahead.less";
|
||||
|
||||
export interface Item {
|
||||
caption: string;
|
||||
value: string;
|
||||
value: any;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,128 +75,170 @@ export interface InputTypeaheadComponentProps {
|
||||
useTextarea?: boolean;
|
||||
}
|
||||
|
||||
interface InputTypeaheadComponentState {
|
||||
isSuggestionVisible: boolean;
|
||||
selectedChoice: Item;
|
||||
filteredChoices: Item[];
|
||||
interface OnClickItem {
|
||||
matchedKey: string;
|
||||
value: any;
|
||||
caption: string;
|
||||
group: string;
|
||||
}
|
||||
|
||||
interface Cache {
|
||||
inputValue: string;
|
||||
selection: Item;
|
||||
}
|
||||
|
||||
interface InputTypeaheadComponentState {}
|
||||
|
||||
export class InputTypeaheadComponent extends React.Component<
|
||||
InputTypeaheadComponentProps,
|
||||
InputTypeaheadComponentState
|
||||
> {
|
||||
constructor(props: InputTypeaheadComponentProps) {
|
||||
private inputElt: HTMLElement;
|
||||
private containerElt: HTMLElement;
|
||||
|
||||
private cache: Cache;
|
||||
private inputValue: string;
|
||||
private selection: Item;
|
||||
|
||||
public constructor(props: InputTypeaheadComponentProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isSuggestionVisible: false,
|
||||
filteredChoices: [],
|
||||
selectedChoice: {
|
||||
caption: "",
|
||||
value: "",
|
||||
},
|
||||
this.cache = {
|
||||
inputValue: null,
|
||||
selection: null,
|
||||
};
|
||||
}
|
||||
|
||||
private onRenderCell = (item: Item): JSX.Element => {
|
||||
return (
|
||||
<div className="input-typeahead-chocies-container" onClick={() => this.onChoiceClick(item)}>
|
||||
<p className="choice-caption">{item.caption}</p>
|
||||
<span>{item.value}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
private onChoiceClick = (item: Item): void => {
|
||||
this.props.onNewValue(item.caption);
|
||||
this.setState({ isSuggestionVisible: false, selectedChoice: item });
|
||||
};
|
||||
|
||||
private handleChange = (value: string): void => {
|
||||
if (!value) {
|
||||
this.setState({ isSuggestionVisible: true });
|
||||
/**
|
||||
* Props have changed
|
||||
* @param prevProps
|
||||
* @param prevState
|
||||
* @param snapshot
|
||||
*/
|
||||
public componentDidUpdate(
|
||||
prevProps: InputTypeaheadComponentProps,
|
||||
prevState: InputTypeaheadComponentState,
|
||||
snapshot: any
|
||||
): void {
|
||||
if (prevProps.defaultValue !== this.props.defaultValue) {
|
||||
$(this.inputElt).val(this.props.defaultValue);
|
||||
this.initializeTypeahead();
|
||||
}
|
||||
this.props.onNewValue(value);
|
||||
const filteredChoices = this.filterChoiceByValue(this.props.choices, value);
|
||||
this.setState({ filteredChoices });
|
||||
};
|
||||
}
|
||||
|
||||
private onSubmit = (event: React.KeyboardEvent<HTMLElement>): void => {
|
||||
if (event.key === "Enter") {
|
||||
/**
|
||||
* Executed once react is done building the DOM for this component
|
||||
*/
|
||||
public componentDidMount(): void {
|
||||
this.initializeTypeahead();
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<span className="input-typeahead-container">
|
||||
<div
|
||||
className="input-typehead"
|
||||
onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onKeyDown(event)}
|
||||
>
|
||||
<div className="typeahead__container" ref={(input) => (this.containerElt = input)}>
|
||||
<div className="typeahead__field">
|
||||
<span className="typeahead__query">
|
||||
{this.props.useTextarea ? (
|
||||
<textarea
|
||||
rows={1}
|
||||
name="q"
|
||||
autoComplete="off"
|
||||
aria-label="Input query"
|
||||
ref={(input) => (this.inputElt = input)}
|
||||
defaultValue={this.props.defaultValue}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
name="q"
|
||||
type="search"
|
||||
autoComplete="off"
|
||||
aria-label="Input query"
|
||||
ref={(input) => (this.inputElt = input)}
|
||||
defaultValue={this.props.defaultValue}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
{this.props.showSearchButton && (
|
||||
<span className="typeahead__button">
|
||||
<button type="submit">
|
||||
<span className="typeahead__search-icon" />
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
private onKeyDown(event: React.KeyboardEvent<HTMLElement>) {
|
||||
if (event.keyCode === KeyCodes.Enter) {
|
||||
if (this.props.submitFct) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.props.submitFct(this.props.defaultValue, this.state.selectedChoice);
|
||||
this.setState({ isSuggestionVisible: false });
|
||||
this.props.submitFct(this.cache.inputValue, this.cache.selection);
|
||||
$(this.containerElt).children(".typeahead__result").hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private filterChoiceByValue = (choices: Item[], searchKeyword: string): Item[] => {
|
||||
return choices.filter((choice) =>
|
||||
// @ts-ignore
|
||||
Object.keys(choice).some((key) => choice[key].toLowerCase().includes(searchKeyword.toLowerCase()))
|
||||
);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
const { defaultValue, useTextarea, placeholder, onNewValue } = this.props;
|
||||
const { isSuggestionVisible, selectedChoice, filteredChoices } = this.state;
|
||||
const theme = getTheme();
|
||||
|
||||
const iconButtonStyles = {
|
||||
root: {
|
||||
color: theme.palette.neutralPrimary,
|
||||
marginLeft: "10px !important",
|
||||
marginTop: "0px",
|
||||
marginRight: "2px",
|
||||
width: "42px",
|
||||
/**
|
||||
* Must execute once ko is rendered, so that it can find the input element by id
|
||||
*/
|
||||
private initializeTypeahead(): void {
|
||||
const props = this.props;
|
||||
let cache = this.cache;
|
||||
let options: any = {
|
||||
input: this.inputElt,
|
||||
order: "asc",
|
||||
minLength: 0,
|
||||
searchOnFocus: true,
|
||||
source: {
|
||||
display: "caption",
|
||||
data: () => {
|
||||
return props.choices;
|
||||
},
|
||||
},
|
||||
rootHovered: {
|
||||
color: theme.palette.neutralDark,
|
||||
callback: {
|
||||
onClick: (node: any, a: any, item: OnClickItem, event: any) => {
|
||||
cache.selection = item;
|
||||
|
||||
if (props.onSelected) {
|
||||
props.onSelected(item);
|
||||
}
|
||||
},
|
||||
onResult(node: any, query: any, result: any, resultCount: any, resultCountPerGroup: any) {
|
||||
cache.inputValue = query;
|
||||
if (props.onNewValue) {
|
||||
props.onNewValue(query);
|
||||
}
|
||||
},
|
||||
},
|
||||
template: (query: string, item: any) => {
|
||||
// Don't display id if caption *IS* the id
|
||||
return item.caption === item.value
|
||||
? "<span>{{caption}}</span>"
|
||||
: "<span><div>{{caption}}</div><div><small>{{value}}</small></div></span>";
|
||||
},
|
||||
dynamic: true,
|
||||
};
|
||||
const cancelIcon: IIconProps = { iconName: "cancel" };
|
||||
const searchIcon: IIconProps = { iconName: "Search" };
|
||||
|
||||
return (
|
||||
<div className="input-typeahead-container">
|
||||
<Stack horizontal>
|
||||
<form aria-labelledby="input" className="input-query-form">
|
||||
<TextField
|
||||
multiline={useTextarea}
|
||||
rows={1}
|
||||
id="input"
|
||||
defaultValue={defaultValue}
|
||||
ariaLabel="Input query"
|
||||
placeholder={placeholder}
|
||||
className="input-type-head-text-field"
|
||||
value={defaultValue}
|
||||
onKeyDown={this.onSubmit}
|
||||
onFocus={() => this.setState({ isSuggestionVisible: true })}
|
||||
onChange={(_event, newValue?: string) => this.handleChange(newValue)}
|
||||
/>
|
||||
</form>
|
||||
{this.props.showCancelButton && (
|
||||
<IconButton
|
||||
styles={iconButtonStyles}
|
||||
iconProps={cancelIcon}
|
||||
ariaLabel="cancel Button"
|
||||
onClick={() => onNewValue("")}
|
||||
/>
|
||||
)}
|
||||
{this.props.showSearchButton && (
|
||||
<IconButton
|
||||
styles={iconButtonStyles}
|
||||
iconProps={searchIcon}
|
||||
ariaLabel="Search Button"
|
||||
onClick={() => this.props.submitFct(defaultValue, selectedChoice)}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
{filteredChoices.length && isSuggestionVisible ? (
|
||||
<List items={filteredChoices} onRenderCell={this.onRenderCell} />
|
||||
) : undefined}
|
||||
</div>
|
||||
);
|
||||
// Override options
|
||||
if (props.typeaheadOverrideOptions) {
|
||||
for (const p in props.typeaheadOverrideOptions) {
|
||||
options[p] = props.typeaheadOverrideOptions[p];
|
||||
}
|
||||
}
|
||||
|
||||
if (props.hasOwnProperty("showCancelButton")) {
|
||||
options.cancelButton = props.showCancelButton;
|
||||
}
|
||||
|
||||
$(this.inputElt).typeahead(options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,61 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`inputTypeahead renders <input /> 1`] = `
|
||||
<div
|
||||
<span
|
||||
className="input-typeahead-container"
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
<div
|
||||
className="input-typehead"
|
||||
onKeyDown={[Function]}
|
||||
>
|
||||
<form
|
||||
aria-labelledby="input"
|
||||
className="input-query-form"
|
||||
<div
|
||||
className="typeahead__container"
|
||||
>
|
||||
<StyledTextFieldBase
|
||||
ariaLabel="Input query"
|
||||
className="input-type-head-text-field"
|
||||
id="input"
|
||||
multiline={false}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
placeholder="placeholder"
|
||||
rows={1}
|
||||
/>
|
||||
</form>
|
||||
</Stack>
|
||||
</div>
|
||||
<div
|
||||
className="typeahead__field"
|
||||
>
|
||||
<span
|
||||
className="typeahead__query"
|
||||
>
|
||||
<input
|
||||
aria-label="Input query"
|
||||
autoComplete="off"
|
||||
name="q"
|
||||
type="search"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`inputTypeahead renders <textarea /> 1`] = `
|
||||
<div
|
||||
<span
|
||||
className="input-typeahead-container"
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
<div
|
||||
className="input-typehead"
|
||||
onKeyDown={[Function]}
|
||||
>
|
||||
<form
|
||||
aria-labelledby="input"
|
||||
className="input-query-form"
|
||||
<div
|
||||
className="typeahead__container"
|
||||
>
|
||||
<StyledTextFieldBase
|
||||
ariaLabel="Input query"
|
||||
className="input-type-head-text-field"
|
||||
id="input"
|
||||
multiline={true}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
placeholder="placeholder"
|
||||
rows={1}
|
||||
/>
|
||||
</form>
|
||||
</Stack>
|
||||
</div>
|
||||
<div
|
||||
className="typeahead__field"
|
||||
>
|
||||
<span
|
||||
className="typeahead__query"
|
||||
>
|
||||
<textarea
|
||||
aria-label="Input query"
|
||||
autoComplete="off"
|
||||
name="q"
|
||||
rows={1}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
`;
|
||||
|
||||
@@ -1,90 +1,154 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import { NotebookTerminalComponent, NotebookTerminalComponentProps } from "./NotebookTerminalComponent";
|
||||
import { NotebookTerminalComponent } from "./NotebookTerminalComponent";
|
||||
|
||||
const testAccount: DataModels.DatabaseAccount = {
|
||||
id: "id",
|
||||
kind: "kind",
|
||||
location: "location",
|
||||
name: "name",
|
||||
properties: {
|
||||
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
|
||||
},
|
||||
type: "type",
|
||||
const createTestDatabaseAccount = (): DataModels.DatabaseAccount => {
|
||||
return {
|
||||
id: "testId",
|
||||
kind: "testKind",
|
||||
location: "testLocation",
|
||||
name: "testName",
|
||||
properties: {
|
||||
cassandraEndpoint: null,
|
||||
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
|
||||
gremlinEndpoint: null,
|
||||
tableEndpoint: null,
|
||||
},
|
||||
type: "testType",
|
||||
};
|
||||
};
|
||||
|
||||
const testMongo32Account: DataModels.DatabaseAccount = {
|
||||
...testAccount,
|
||||
const createTestMongo32DatabaseAccount = (): DataModels.DatabaseAccount => {
|
||||
return {
|
||||
id: "testId",
|
||||
kind: "testKind",
|
||||
location: "testLocation",
|
||||
name: "testName",
|
||||
properties: {
|
||||
cassandraEndpoint: null,
|
||||
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
|
||||
gremlinEndpoint: null,
|
||||
tableEndpoint: null,
|
||||
},
|
||||
type: "testType",
|
||||
};
|
||||
};
|
||||
|
||||
const testMongo36Account: DataModels.DatabaseAccount = {
|
||||
...testAccount,
|
||||
properties: {
|
||||
mongoEndpoint: "https://testMongoEndpoint.azure.com/",
|
||||
},
|
||||
const createTestMongo36DatabaseAccount = (): DataModels.DatabaseAccount => {
|
||||
return {
|
||||
id: "testId",
|
||||
kind: "testKind",
|
||||
location: "testLocation",
|
||||
name: "testName",
|
||||
properties: {
|
||||
cassandraEndpoint: null,
|
||||
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
|
||||
gremlinEndpoint: null,
|
||||
tableEndpoint: null,
|
||||
mongoEndpoint: "https://testMongoEndpoint.azure.com/",
|
||||
},
|
||||
type: "testType",
|
||||
};
|
||||
};
|
||||
|
||||
const testCassandraAccount: DataModels.DatabaseAccount = {
|
||||
...testAccount,
|
||||
properties: {
|
||||
cassandraEndpoint: "https://testCassandraEndpoint.azure.com/",
|
||||
},
|
||||
const createTestCassandraDatabaseAccount = (): DataModels.DatabaseAccount => {
|
||||
return {
|
||||
id: "testId",
|
||||
kind: "testKind",
|
||||
location: "testLocation",
|
||||
name: "testName",
|
||||
properties: {
|
||||
cassandraEndpoint: "https://testCassandraEndpoint.azure.com/",
|
||||
documentEndpoint: null,
|
||||
gremlinEndpoint: null,
|
||||
tableEndpoint: null,
|
||||
},
|
||||
type: "testType",
|
||||
};
|
||||
};
|
||||
|
||||
const testNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||
authToken: "authToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com",
|
||||
const createTerminal = (): NotebookTerminalComponent => {
|
||||
return new NotebookTerminalComponent({
|
||||
notebookServerInfo: {
|
||||
authToken: "testAuthToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/",
|
||||
},
|
||||
databaseAccount: createTestDatabaseAccount(),
|
||||
});
|
||||
};
|
||||
|
||||
const testMongoNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||
authToken: "authToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
|
||||
const createMongo32Terminal = (): NotebookTerminalComponent => {
|
||||
return new NotebookTerminalComponent({
|
||||
notebookServerInfo: {
|
||||
authToken: "testAuthToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
|
||||
},
|
||||
databaseAccount: createTestMongo32DatabaseAccount(),
|
||||
});
|
||||
};
|
||||
|
||||
const testCassandraNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||
authToken: "authToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/cassandra",
|
||||
const createMongo36Terminal = (): NotebookTerminalComponent => {
|
||||
return new NotebookTerminalComponent({
|
||||
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",
|
||||
},
|
||||
databaseAccount: createTestCassandraDatabaseAccount(),
|
||||
});
|
||||
};
|
||||
|
||||
describe("NotebookTerminalComponent", () => {
|
||||
it("renders terminal", () => {
|
||||
const props: NotebookTerminalComponentProps = {
|
||||
databaseAccount: testAccount,
|
||||
notebookServerInfo: testNotebookServerInfo,
|
||||
};
|
||||
it("getTerminalParams: Test for terminal", () => {
|
||||
const terminal: NotebookTerminalComponent = createTerminal();
|
||||
const params: Map<string, string> = terminal.getTerminalParams();
|
||||
|
||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(params).toEqual(
|
||||
new Map<string, string>([["terminal", "true"]])
|
||||
);
|
||||
});
|
||||
|
||||
it("renders mongo 3.2 shell", () => {
|
||||
const props: NotebookTerminalComponentProps = {
|
||||
databaseAccount: testMongo32Account,
|
||||
notebookServerInfo: testMongoNotebookServerInfo,
|
||||
};
|
||||
it("getTerminalParams: Test for Mongo 3.2 terminal", () => {
|
||||
const terminal: NotebookTerminalComponent = createMongo32Terminal();
|
||||
const params: Map<string, string> = terminal.getTerminalParams();
|
||||
|
||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(params).toEqual(
|
||||
new Map<string, string>([
|
||||
["terminal", "true"],
|
||||
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.documentEndpoint).host],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it("renders mongo 3.6 shell", () => {
|
||||
const props: NotebookTerminalComponentProps = {
|
||||
databaseAccount: testMongo36Account,
|
||||
notebookServerInfo: testMongoNotebookServerInfo,
|
||||
};
|
||||
it("getTerminalParams: Test for Mongo 3.6 terminal", () => {
|
||||
const terminal: NotebookTerminalComponent = createMongo36Terminal();
|
||||
const params: Map<string, string> = terminal.getTerminalParams();
|
||||
|
||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(params).toEqual(
|
||||
new Map<string, string>([
|
||||
["terminal", "true"],
|
||||
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.mongoEndpoint).host],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it("renders cassandra shell", () => {
|
||||
const props: NotebookTerminalComponentProps = {
|
||||
databaseAccount: testCassandraAccount,
|
||||
notebookServerInfo: testCassandraNotebookServerInfo,
|
||||
};
|
||||
it("getTerminalParams: Test for Cassandra terminal", () => {
|
||||
const terminal: NotebookTerminalComponent = createCassandraTerminal();
|
||||
const params: Map<string, string> = terminal.getTerminalParams();
|
||||
|
||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(params).toEqual(
|
||||
new Map<string, string>([
|
||||
["terminal", "true"],
|
||||
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.cassandraEndpoint).host],
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
* Wrapper around Notebook server terminal
|
||||
*/
|
||||
|
||||
import postRobot from "post-robot";
|
||||
import * as React from "react";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import { TerminalProps } from "../../../Terminal/TerminalProps";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import * as StringUtils from "../../../Utils/StringUtils";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { TerminalQueryParams } from "../../../Common/Constants";
|
||||
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||
|
||||
export interface NotebookTerminalComponentProps {
|
||||
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
||||
@@ -15,69 +15,79 @@ export interface NotebookTerminalComponentProps {
|
||||
}
|
||||
|
||||
export class NotebookTerminalComponent extends React.Component<NotebookTerminalComponentProps> {
|
||||
private terminalWindow: Window;
|
||||
|
||||
constructor(props: NotebookTerminalComponentProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.sendPropsToTerminalFrame();
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<div className="notebookTerminalContainer">
|
||||
<iframe
|
||||
title="Terminal to Notebook Server"
|
||||
onLoad={(event) => this.handleFrameLoad(event)}
|
||||
src="terminal.html"
|
||||
src={NotebookTerminalComponent.createNotebookAppSrc(this.props.notebookServerInfo, this.getTerminalParams())}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleFrameLoad(event: React.SyntheticEvent<HTMLIFrameElement, Event>): void {
|
||||
this.terminalWindow = (event.target as HTMLIFrameElement).contentWindow;
|
||||
this.sendPropsToTerminalFrame();
|
||||
}
|
||||
public getTerminalParams(): Map<string, string> {
|
||||
let params: Map<string, string> = new Map<string, string>();
|
||||
params.set(TerminalQueryParams.Terminal, "true");
|
||||
|
||||
sendPropsToTerminalFrame(): void {
|
||||
if (!this.terminalWindow) {
|
||||
return;
|
||||
const terminalEndpoint: string = this.tryGetTerminalEndpoint();
|
||||
if (terminalEndpoint) {
|
||||
params.set(TerminalQueryParams.TerminalEndpoint, terminalEndpoint);
|
||||
}
|
||||
|
||||
const props: TerminalProps = {
|
||||
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,
|
||||
};
|
||||
|
||||
postRobot.send(this.terminalWindow, "props", props, {
|
||||
domain: window.location.origin,
|
||||
});
|
||||
return params;
|
||||
}
|
||||
|
||||
public tryGetTerminalEndpoint(): string | undefined {
|
||||
let terminalEndpoint: string | undefined;
|
||||
public tryGetTerminalEndpoint(): string | null {
|
||||
let terminalEndpoint: string | null;
|
||||
|
||||
const notebookServerEndpoint = this.props.notebookServerInfo?.notebookServerEndpoint;
|
||||
const notebookServerEndpoint: string = this.props.notebookServerInfo.notebookServerEndpoint;
|
||||
if (StringUtils.endsWith(notebookServerEndpoint, "mongo")) {
|
||||
// mongoEndpoint is only available for Mongo 3.6 and higher, fallback to documentEndpoint otherwise
|
||||
terminalEndpoint =
|
||||
this.props.databaseAccount?.properties.mongoEndpoint || this.props.databaseAccount?.properties.documentEndpoint;
|
||||
let mongoShellEndpoint: string = this.props.databaseAccount.properties.mongoEndpoint;
|
||||
if (!mongoShellEndpoint) {
|
||||
// mongoEndpoint is only available for Mongo 3.6 and higher.
|
||||
// Fallback to documentEndpoint otherwise.
|
||||
mongoShellEndpoint = this.props.databaseAccount.properties.documentEndpoint;
|
||||
}
|
||||
terminalEndpoint = mongoShellEndpoint;
|
||||
} else if (StringUtils.endsWith(notebookServerEndpoint, "cassandra")) {
|
||||
terminalEndpoint = this.props.databaseAccount?.properties.cassandraEndpoint;
|
||||
terminalEndpoint = this.props.databaseAccount.properties.cassandraEndpoint;
|
||||
}
|
||||
|
||||
if (terminalEndpoint) {
|
||||
return new URL(terminalEndpoint).host;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
public static createNotebookAppSrc(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`NotebookTerminalComponent renders cassandra shell 1`] = `
|
||||
<div
|
||||
className="notebookTerminalContainer"
|
||||
>
|
||||
<iframe
|
||||
onLoad={[Function]}
|
||||
src="terminal.html"
|
||||
title="Terminal to Notebook Server"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`NotebookTerminalComponent renders mongo 3.2 shell 1`] = `
|
||||
<div
|
||||
className="notebookTerminalContainer"
|
||||
>
|
||||
<iframe
|
||||
onLoad={[Function]}
|
||||
src="terminal.html"
|
||||
title="Terminal to Notebook Server"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`NotebookTerminalComponent renders mongo 3.6 shell 1`] = `
|
||||
<div
|
||||
className="notebookTerminalContainer"
|
||||
>
|
||||
<iframe
|
||||
onLoad={[Function]}
|
||||
src="terminal.html"
|
||||
title="Terminal to Notebook Server"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`NotebookTerminalComponent renders terminal 1`] = `
|
||||
<div
|
||||
className="notebookTerminalContainer"
|
||||
>
|
||||
<iframe
|
||||
onLoad={[Function]}
|
||||
src="terminal.html"
|
||||
title="Terminal to Notebook Server"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -17,8 +17,6 @@ import Explorer from "../../Explorer";
|
||||
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
|
||||
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
|
||||
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
|
||||
import { NotebookUtil } from "../../Notebook/NotebookUtil";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
import { Dialog, TextFieldProps, useDialog } from "../Dialog";
|
||||
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
|
||||
import "./NotebookViewerComponent.less";
|
||||
@@ -148,9 +146,7 @@ export class NotebookViewerComponent
|
||||
<NotebookMetadataComponent
|
||||
data={this.state.galleryItem}
|
||||
isFavorite={this.state.isFavorite}
|
||||
downloadButtonText={
|
||||
this.props.container && NotebookUtil.getNotebookBtnTitle(useNotebook.getState().notebookFolderName)
|
||||
}
|
||||
downloadButtonText={this.props.container && "Download to my notebooks"}
|
||||
onTagClick={this.props.onTagClick}
|
||||
onFavoriteClick={this.favoriteItem}
|
||||
onUnfavoriteClick={this.unfavoriteItem}
|
||||
|
||||
@@ -29,9 +29,8 @@ import { QueriesClient } from "../../../Common/QueriesClient";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { useDialog } from "../Dialog";
|
||||
|
||||
const title = "Open Saved Queries";
|
||||
const title: string = "Open Saved Queries";
|
||||
|
||||
export interface QueriesGridComponentProps {
|
||||
queriesClient: QueriesClient;
|
||||
@@ -197,9 +196,9 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
|
||||
{
|
||||
key: "Action",
|
||||
name: "Action",
|
||||
fieldName: undefined,
|
||||
fieldName: null,
|
||||
minWidth: 70,
|
||||
onRender: (query: Query) => {
|
||||
onRender: (query: Query, index: number, column: IColumn) => {
|
||||
const buttonProps: IButtonProps = {
|
||||
iconProps: {
|
||||
iconName: "More",
|
||||
@@ -215,50 +214,47 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
|
||||
{
|
||||
key: "Open",
|
||||
text: "Open query",
|
||||
onClick: () => {
|
||||
onClick: (event: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>, menuItem: any) => {
|
||||
this.props.onQuerySelect(query);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "Delete",
|
||||
text: "Delete query",
|
||||
onClick: async () => {
|
||||
useDialog.getState().showOkCancelModalDialog(
|
||||
"Confirm delete",
|
||||
"Are you sure you want to delete this query?",
|
||||
"Delete",
|
||||
async () => {
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteSavedQuery, {
|
||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||
paneTitle: title,
|
||||
});
|
||||
try {
|
||||
await this.props.queriesClient.deleteQuery(query);
|
||||
TelemetryProcessor.traceSuccess(
|
||||
Action.DeleteSavedQuery,
|
||||
{
|
||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||
paneTitle: title,
|
||||
},
|
||||
startKey
|
||||
);
|
||||
} catch (error) {
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.DeleteSavedQuery,
|
||||
{
|
||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||
paneTitle: title,
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error),
|
||||
},
|
||||
startKey
|
||||
);
|
||||
}
|
||||
await this.fetchSavedQueries(); // get latest state
|
||||
},
|
||||
"Cancel",
|
||||
undefined
|
||||
);
|
||||
onClick: async (
|
||||
event: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
|
||||
menuItem: any
|
||||
) => {
|
||||
if (window.confirm("Are you sure you want to delete this query?")) {
|
||||
const container = window.dataExplorer;
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteSavedQuery, {
|
||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||
paneTitle: title,
|
||||
});
|
||||
try {
|
||||
await this.props.queriesClient.deleteQuery(query);
|
||||
TelemetryProcessor.traceSuccess(
|
||||
Action.DeleteSavedQuery,
|
||||
{
|
||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||
paneTitle: title,
|
||||
},
|
||||
startKey
|
||||
);
|
||||
} catch (error) {
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.DeleteSavedQuery,
|
||||
{
|
||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||
paneTitle: title,
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error),
|
||||
},
|
||||
startKey
|
||||
);
|
||||
}
|
||||
await this.fetchSavedQueries(); // get latest state
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -16,6 +16,7 @@ import { trace, traceFailure, traceStart, traceSuccess } from "../../../Shared/T
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
|
||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
|
||||
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
|
||||
@@ -109,6 +110,7 @@ export interface SettingsComponentState {
|
||||
|
||||
initialNotification: DataModels.Notification;
|
||||
selectedTab: SettingsV2TabTypes;
|
||||
offerLoaded: boolean;
|
||||
}
|
||||
|
||||
export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> {
|
||||
@@ -193,6 +195,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
|
||||
initialNotification: undefined,
|
||||
selectedTab: SettingsV2TabTypes.ScaleTab,
|
||||
offerLoaded: !!this.offer,
|
||||
};
|
||||
|
||||
this.saveSettingsButton = {
|
||||
@@ -214,6 +217,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
if (this.isCollectionSettingsTab) {
|
||||
this.refreshIndexTransformationProgress();
|
||||
this.loadMongoIndexes();
|
||||
this.loadCollectionOffer();
|
||||
}
|
||||
|
||||
this.setAutoPilotStates();
|
||||
@@ -368,6 +372,34 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
});
|
||||
};
|
||||
|
||||
private async loadCollectionOffer() {
|
||||
try {
|
||||
this.props.settingsTab.isExecuting(true);
|
||||
await this.collection.loadOffer();
|
||||
this.props.settingsTab.tabTitle(this.collection.offer() ? "Settings" : "Scale & Settings");
|
||||
this.setState({ offerLoaded: true });
|
||||
} catch (error) {
|
||||
this.props.settingsTab.isExecutionError(true);
|
||||
const errorMessage = getErrorMessage(error);
|
||||
traceFailure(
|
||||
Action.Tab,
|
||||
{
|
||||
databaseName: this.collection.databaseId,
|
||||
collectionName: this.collection.id(),
|
||||
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: this.props.settingsTab.tabTitle,
|
||||
error: errorMessage,
|
||||
errorStack: getErrorStack(error),
|
||||
},
|
||||
this.props.settingsTab.onLoadStartKey
|
||||
);
|
||||
logConsoleError(`Error while fetching container settings for container ${this.collection.id()}: ${errorMessage}`);
|
||||
} finally {
|
||||
this.props.settingsTab.isExecuting(false);
|
||||
}
|
||||
}
|
||||
|
||||
private getMongoIndexesToSave = (): MongoIndex[] => {
|
||||
let finalIndexes: MongoIndex[] = [];
|
||||
this.state.currentMongoIndexes?.map((mongoIndex: MongoIndex, index: number) => {
|
||||
@@ -905,6 +937,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.state.offerLoaded) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const subSettingsComponentProps: SubSettingsComponentProps = {
|
||||
collection: this.collection,
|
||||
isAnalyticalStorageEnabled: this.isAnalyticalStorageEnabled,
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
import * as React from "react";
|
||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||
import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants";
|
||||
import { Urls, StyleConstants } from "../../../Common/Constants";
|
||||
import {
|
||||
getPriceCurrency,
|
||||
getCurrencySign,
|
||||
getAutoscalePricePerRu,
|
||||
getMultimasterMultiplier,
|
||||
computeRUUsagePriceHourly,
|
||||
getPricePerRu,
|
||||
estimatedCostDisclaimer,
|
||||
} from "../../../Utils/PricingUtils";
|
||||
import {
|
||||
DetailsList,
|
||||
DetailsListLayoutMode,
|
||||
DetailsRow,
|
||||
ICheckboxStyles,
|
||||
IChoiceGroupStyles,
|
||||
IColumn,
|
||||
IDetailsColumnStyles,
|
||||
IDetailsListStyles,
|
||||
IDetailsRowProps,
|
||||
IDetailsRowStyles,
|
||||
IDropdownStyles,
|
||||
IMessageBarStyles,
|
||||
ISeparatorStyles,
|
||||
IStackProps,
|
||||
IStackStyles,
|
||||
IStackTokens,
|
||||
ITextFieldStyles,
|
||||
ITextStyles,
|
||||
ICheckboxStyles,
|
||||
IStackProps,
|
||||
IStackTokens,
|
||||
IChoiceGroupStyles,
|
||||
Link,
|
||||
Text,
|
||||
IMessageBarStyles,
|
||||
ITextStyles,
|
||||
IDetailsRowStyles,
|
||||
IStackStyles,
|
||||
IDetailsListStyles,
|
||||
IDropdownStyles,
|
||||
ISeparatorStyles,
|
||||
MessageBar,
|
||||
MessageBarType,
|
||||
SelectionMode,
|
||||
Stack,
|
||||
Spinner,
|
||||
SpinnerSize,
|
||||
Stack,
|
||||
Text,
|
||||
DetailsList,
|
||||
IColumn,
|
||||
SelectionMode,
|
||||
DetailsListLayoutMode,
|
||||
IDetailsRowProps,
|
||||
DetailsRow,
|
||||
IDetailsColumnStyles,
|
||||
} from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import { StyleConstants, Urls } from "../../../Common/Constants";
|
||||
import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants";
|
||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||
import {
|
||||
computeRUUsagePriceHourly,
|
||||
estimatedCostDisclaimer,
|
||||
getAutoscalePricePerRu,
|
||||
getCurrencySign,
|
||||
getMultimasterMultiplier,
|
||||
getPriceCurrency,
|
||||
getPricePerRu,
|
||||
} from "../../../Utils/PricingUtils";
|
||||
import { isDirty, isDirtyTypes } from "./SettingsUtils";
|
||||
import { isDirtyTypes, isDirty } from "./SettingsUtils";
|
||||
|
||||
export interface EstimatedSpendingDisplayProps {
|
||||
costType: JSX.Element;
|
||||
@@ -65,7 +65,7 @@ export interface PriceBreakdown {
|
||||
currencySign: string;
|
||||
}
|
||||
|
||||
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14, color: "windowtext" } };
|
||||
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14 } };
|
||||
|
||||
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
||||
label: {
|
||||
@@ -223,15 +223,14 @@ export const getRuPriceBreakdown = (
|
||||
multimasterEnabled: isMultimaster,
|
||||
isAutoscale: isAutoscale,
|
||||
});
|
||||
const multimasterMultiplier = getMultimasterMultiplier(numberOfRegions, isMultimaster);
|
||||
const pricePerRu: number = isAutoscale
|
||||
? getAutoscalePricePerRu(serverId, multimasterMultiplier)
|
||||
: getPricePerRu(serverId, multimasterMultiplier);
|
||||
const basePricePerRu: number = isAutoscale
|
||||
? getAutoscalePricePerRu(serverId, getMultimasterMultiplier(numberOfRegions, isMultimaster))
|
||||
: getPricePerRu(serverId);
|
||||
return {
|
||||
hourlyPrice,
|
||||
hourlyPrice: hourlyPrice,
|
||||
dailyPrice: hourlyPrice * 24,
|
||||
monthlyPrice: hourlyPrice * hoursInAMonth,
|
||||
pricePerRu,
|
||||
pricePerRu: basePricePerRu * getMultimasterMultiplier(numberOfRegions, isMultimaster),
|
||||
currency: getPriceCurrency(serverId),
|
||||
currencySign: getCurrencySign(serverId),
|
||||
};
|
||||
@@ -272,7 +271,7 @@ export const manualToAutoscaleDisclaimerElement: JSX.Element = (
|
||||
<Text styles={infoAndToolTipTextStyle} id="manualToAutoscaleDisclaimerElement">
|
||||
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings
|
||||
and storage of your resource. After autoscale has been enabled, you can change the max RU/s.{" "}
|
||||
<Link href={Urls.autoscaleMigration}>Learn more</Link>
|
||||
<a href={Urls.autoscaleMigration}>Learn more</a>
|
||||
</Text>
|
||||
);
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ exports[`IndexingPolicyRefreshComponent renders 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -40,7 +39,6 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -75,7 +73,6 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -83,11 +80,11 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
||||
>
|
||||
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s.
|
||||
|
||||
<StyledLinkBase
|
||||
<a
|
||||
href="https://aka.ms/cosmos-autoscale-migration"
|
||||
>
|
||||
Learn more
|
||||
</StyledLinkBase>
|
||||
</a>
|
||||
</Text>
|
||||
</StyledMessageBar>
|
||||
<StyledChoiceGroup
|
||||
@@ -189,7 +186,6 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -464,7 +460,6 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ exports[`ScaleComponent renders with correct initial notification 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -136,7 +136,6 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -413,7 +412,6 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -954,7 +952,6 @@ exports[`SubSettingsComponent renders 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -1231,7 +1228,6 @@ exports[`SubSettingsComponent timeToLiveSeconds hidden 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -30,21 +30,39 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"container": Explorer {
|
||||
"_isInitializingNotebooks": false,
|
||||
"_resetNotebookWorkspace": [Function],
|
||||
"isAccountReady": [Function],
|
||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||
"isNotebookEnabled": [Function],
|
||||
"isNotebooksEnabledForAccount": [Function],
|
||||
"isSchemaEnabled": [Function],
|
||||
"isShellEnabled": [Function],
|
||||
"isSynapseLinkUpdating": [Function],
|
||||
"isTabsContentExpanded": [Function],
|
||||
"memoryUsageInfo": [Function],
|
||||
"notebookBasePath": [Function],
|
||||
"notebookServerInfo": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {},
|
||||
"provideFeedbackEmail": [Function],
|
||||
"queriesClient": QueriesClient {
|
||||
"container": [Circular],
|
||||
},
|
||||
"refreshNotebookList": [Function],
|
||||
"resourceTokenCollection": [Function],
|
||||
"resourceTree": ResourceTreeAdapter {
|
||||
"container": [Circular],
|
||||
"copyNotebook": [Function],
|
||||
"parameters": [Function],
|
||||
},
|
||||
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
||||
"container": [Circular],
|
||||
"parameters": [Function],
|
||||
},
|
||||
"sparkClusterConnectionInfo": [Function],
|
||||
"tabsManager": TabsManager {
|
||||
"activeTab": [Function],
|
||||
"openedTabs": [Function],
|
||||
},
|
||||
},
|
||||
"databaseId": "test",
|
||||
"defaultTtl": [Function],
|
||||
@@ -98,21 +116,39 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"container": Explorer {
|
||||
"_isInitializingNotebooks": false,
|
||||
"_resetNotebookWorkspace": [Function],
|
||||
"isAccountReady": [Function],
|
||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||
"isNotebookEnabled": [Function],
|
||||
"isNotebooksEnabledForAccount": [Function],
|
||||
"isSchemaEnabled": [Function],
|
||||
"isShellEnabled": [Function],
|
||||
"isSynapseLinkUpdating": [Function],
|
||||
"isTabsContentExpanded": [Function],
|
||||
"memoryUsageInfo": [Function],
|
||||
"notebookBasePath": [Function],
|
||||
"notebookServerInfo": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {},
|
||||
"provideFeedbackEmail": [Function],
|
||||
"queriesClient": QueriesClient {
|
||||
"container": [Circular],
|
||||
},
|
||||
"refreshNotebookList": [Function],
|
||||
"resourceTokenCollection": [Function],
|
||||
"resourceTree": ResourceTreeAdapter {
|
||||
"container": [Circular],
|
||||
"copyNotebook": [Function],
|
||||
"parameters": [Function],
|
||||
},
|
||||
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
||||
"container": [Circular],
|
||||
"parameters": [Function],
|
||||
},
|
||||
"sparkClusterConnectionInfo": [Function],
|
||||
"tabsManager": TabsManager {
|
||||
"activeTab": [Function],
|
||||
"openedTabs": [Function],
|
||||
},
|
||||
},
|
||||
"databaseId": "test",
|
||||
"defaultTtl": [Function],
|
||||
|
||||
@@ -159,7 +159,6 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -167,17 +166,16 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
>
|
||||
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s.
|
||||
|
||||
<StyledLinkBase
|
||||
<a
|
||||
href="https://aka.ms/cosmos-autoscale-migration"
|
||||
>
|
||||
Learn more
|
||||
</StyledLinkBase>
|
||||
</a>
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -197,7 +195,6 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -210,7 +207,6 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -223,7 +219,6 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -235,7 +230,6 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -255,7 +249,6 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -271,7 +264,6 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -286,7 +278,6 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -300,7 +291,6 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -312,7 +302,6 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -332,7 +321,6 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -375,7 +363,6 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -391,7 +378,6 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
@@ -408,7 +394,6 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@import "../../../../less/Common/Constants";
|
||||
|
||||
.tabComponentContainer {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
.flex-display();
|
||||
.flex-direction();
|
||||
|
||||
@@ -58,7 +58,7 @@ export class TabComponent extends React.Component<TabComponentProps> {
|
||||
as="span"
|
||||
className={className}
|
||||
role="presentation"
|
||||
onActivated={() => this.setActiveTab(index)}
|
||||
onActivated={(e) => this.setActiveTab(index)}
|
||||
aria-label={`Select tab: ${tab.title}`}
|
||||
>
|
||||
{tab.title}
|
||||
|
||||
@@ -6,7 +6,6 @@ import { userContext } from "../../../../UserContext";
|
||||
import {
|
||||
calculateEstimateNumber,
|
||||
computeRUUsagePriceHourly,
|
||||
estimatedCostDisclaimer,
|
||||
getAutoscalePricePerRu,
|
||||
getCurrencySign,
|
||||
getMultimasterMultiplier,
|
||||
@@ -43,9 +42,11 @@ export const CostEstimateText: FunctionComponent<CostEstimateTextProps> = ({
|
||||
const currency: string = getPriceCurrency(serverId);
|
||||
const currencySign: string = getCurrencySign(serverId);
|
||||
const multiplier = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
||||
const pricePerRu = isAutoscale ? getAutoscalePricePerRu(serverId, multiplier) : getPricePerRu(serverId, multiplier);
|
||||
const pricePerRu = isAutoscale
|
||||
? getAutoscalePricePerRu(serverId, multiplier) * multiplier
|
||||
: getPricePerRu(serverId) * multiplier;
|
||||
|
||||
const iconWithEstimatedCostDisclaimer: JSX.Element = <InfoTooltip>{estimatedCostDisclaimer}</InfoTooltip>;
|
||||
const iconWithEstimatedCostDisclaimer: JSX.Element = <InfoTooltip>PricingUtils.estimatedCostDisclaimer</InfoTooltip>;
|
||||
|
||||
if (isAutoscale) {
|
||||
return (
|
||||
|
||||
@@ -118,7 +118,6 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
<input
|
||||
className="throughputInputRadioBtn"
|
||||
aria-label="Autoscale mode"
|
||||
aria-required={true}
|
||||
checked={isAutoscaleSelected}
|
||||
type="radio"
|
||||
role="radio"
|
||||
@@ -132,7 +131,6 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
aria-label="Manual mode"
|
||||
checked={!isAutoscaleSelected}
|
||||
type="radio"
|
||||
aria-required={true}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={(e) => handleOnChangeMode(e, "Manual")}
|
||||
|
||||
@@ -345,14 +345,12 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
ariaLabel="Info"
|
||||
className="panelInfoIcon"
|
||||
iconName="Info"
|
||||
tabIndex={0}
|
||||
>
|
||||
<IconBase
|
||||
ariaLabel="Info"
|
||||
className="panelInfoIcon"
|
||||
iconName="Info"
|
||||
styles={[Function]}
|
||||
tabIndex={0}
|
||||
theme={
|
||||
Object {
|
||||
"disableGlobalClassNames": false,
|
||||
@@ -632,7 +630,6 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
className="panelInfoIcon root-57"
|
||||
data-icon-name="Info"
|
||||
role="img"
|
||||
tabIndex={0}
|
||||
>
|
||||
|
||||
</i>
|
||||
@@ -654,7 +651,6 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
>
|
||||
<input
|
||||
aria-label="Autoscale mode"
|
||||
aria-required={true}
|
||||
checked={true}
|
||||
className="throughputInputRadioBtn"
|
||||
key=".0:$.0"
|
||||
@@ -671,7 +667,6 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
</span>
|
||||
<input
|
||||
aria-label="Manual mode"
|
||||
aria-required={true}
|
||||
checked={false}
|
||||
className="throughputInputRadioBtn"
|
||||
key=".0:$.2"
|
||||
@@ -1332,14 +1327,12 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
ariaLabel="Info"
|
||||
className="panelInfoIcon"
|
||||
iconName="Info"
|
||||
tabIndex={0}
|
||||
>
|
||||
<IconBase
|
||||
ariaLabel="Info"
|
||||
className="panelInfoIcon"
|
||||
iconName="Info"
|
||||
styles={[Function]}
|
||||
tabIndex={0}
|
||||
theme={
|
||||
Object {
|
||||
"disableGlobalClassNames": false,
|
||||
@@ -1619,7 +1612,6 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
className="panelInfoIcon root-57"
|
||||
data-icon-name="Info"
|
||||
role="img"
|
||||
tabIndex={0}
|
||||
>
|
||||
|
||||
</i>
|
||||
|
||||
@@ -243,7 +243,6 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
||||
<div ref={this.contextMenuRef} onContextMenu={this.onRightClick} onKeyPress={this.onMoreButtonKeyPress}>
|
||||
<IconButton
|
||||
name="More"
|
||||
title="More"
|
||||
className="treeMenuEllipsis"
|
||||
ariaLabel={menuItemLabel}
|
||||
menuIconProps={{
|
||||
|
||||
@@ -211,7 +211,6 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
|
||||
},
|
||||
}
|
||||
}
|
||||
title="More"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -424,7 +423,6 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
|
||||
},
|
||||
}
|
||||
}
|
||||
title="More"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,7 @@ describe("DataSampleUtils", () => {
|
||||
collections: ko.observableArray<Collection>([collection]),
|
||||
} as Database;
|
||||
const explorer = {} as Explorer;
|
||||
explorer.showOkModalDialog = () => {};
|
||||
useDatabases.getState().addDatabases([database]);
|
||||
const dataSamplesUtil = new DataSamplesUtil(explorer);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||
import { useDialog } from "../Controls/Dialog";
|
||||
import Explorer from "../Explorer";
|
||||
import { useDatabases } from "../useDatabases";
|
||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||
@@ -21,7 +20,7 @@ export class DataSamplesUtil {
|
||||
const containerName = generator.getCollectionId();
|
||||
if (this.hasContainer(databaseName, containerName, useDatabases.getState().databases)) {
|
||||
const msg = `The container ${containerName} in database ${databaseName} already exists. Please delete it and retry.`;
|
||||
useDialog.getState().showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
|
||||
this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
|
||||
logConsoleError(msg);
|
||||
return;
|
||||
}
|
||||
@@ -30,7 +29,7 @@ export class DataSamplesUtil {
|
||||
.createSampleContainerAsync()
|
||||
.catch((error) => logConsoleError(`Error creating sample container: ${error}`));
|
||||
const msg = `The sample ${containerName} in database ${databaseName} has been successfully created.`;
|
||||
useDialog.getState().showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
|
||||
this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
|
||||
logConsoleInfo(msg);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
||||
import DeleteIcon from "../../../../images/delete.svg";
|
||||
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
|
||||
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
|
||||
import { EditedEdges, GraphNewEdgeData, NeighborVertexBasicInfo, PossibleVertex } from "./GraphExplorer";
|
||||
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 { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
|
||||
|
||||
export interface EditorNeighborsComponentProps {
|
||||
isSource: boolean;
|
||||
@@ -83,11 +83,11 @@ export class EditorNeighborsComponent extends React.Component<EditorNeighborsCom
|
||||
}
|
||||
|
||||
private removeCurrentNeighborEdge(index: number): void {
|
||||
const sources = this.props.editedNeighbors.currentNeighbors;
|
||||
const id = sources[index].edgeId;
|
||||
let sources = this.props.editedNeighbors.currentNeighbors;
|
||||
let id = sources[index].edgeId;
|
||||
sources.splice(index, 1);
|
||||
|
||||
const droppedIds = this.props.editedNeighbors.droppedIds;
|
||||
let droppedIds = this.props.editedNeighbors.droppedIds;
|
||||
droppedIds.push(id);
|
||||
this.onUpdateEdges();
|
||||
}
|
||||
@@ -215,7 +215,7 @@ export class EditorNeighborsComponent extends React.Component<EditorNeighborsCom
|
||||
</td>
|
||||
<td className="actionCol">
|
||||
<span className="rightPaneTrashIcon rightPaneBtns">
|
||||
<img src={DeleteIcon} alt="Delete" onClick={() => this.removeAddedEdgeToNeighbor(index)} />
|
||||
<img src={DeleteIcon} alt="Delete" onClick={(e) => this.removeAddedEdgeToNeighbor(index)} />
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
import { GraphHighlightedNodeData, EditedProperties } from "./GraphExplorer";
|
||||
|
||||
import { EditorNodePropertiesComponent, EditorNodePropertiesComponentProps } from "./EditorNodePropertiesComponent";
|
||||
|
||||
describe("<EditorNodePropertiesComponent />", () => {
|
||||
// Tests that: single value prop is rendered with a textbox and a delete button
|
||||
// multi-value prop only a delete button (cannot be edited)
|
||||
const onUpdateProperties = jest.fn();
|
||||
it("renders component", () => {
|
||||
const props: EditorNodePropertiesComponentProps = {
|
||||
editedProperties: {
|
||||
@@ -23,6 +24,7 @@ describe("<EditorNodePropertiesComponent />", () => {
|
||||
{ value: true, type: "boolean" },
|
||||
{ value: false, type: "boolean" },
|
||||
{ value: undefined, type: "null" },
|
||||
{ value: null, type: "null" },
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -39,13 +41,14 @@ describe("<EditorNodePropertiesComponent />", () => {
|
||||
{ value: true, type: "boolean" },
|
||||
{ value: false, type: "boolean" },
|
||||
{ value: undefined, type: "null" },
|
||||
{ value: null, type: "null" },
|
||||
],
|
||||
},
|
||||
],
|
||||
addedProperties: [],
|
||||
droppedKeys: [],
|
||||
},
|
||||
onUpdateProperties,
|
||||
onUpdateProperties: (editedProperties: EditedProperties): void => {},
|
||||
};
|
||||
const wrapper = shallow(<EditorNodePropertiesComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
@@ -78,7 +81,7 @@ describe("<EditorNodePropertiesComponent />", () => {
|
||||
addedProperties: [],
|
||||
droppedKeys: [],
|
||||
},
|
||||
onUpdateProperties,
|
||||
onUpdateProperties: (editedProperties: EditedProperties): void => {},
|
||||
};
|
||||
const wrapper = shallow(<EditorNodePropertiesComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import AddIcon from "../../../../images/Add-property.svg";
|
||||
import DeleteIcon from "../../../../images/delete.svg";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
|
||||
import { EditedProperties } from "./GraphExplorer";
|
||||
import DeleteIcon from "../../../../images/delete.svg";
|
||||
import AddIcon from "../../../../images/Add-property.svg";
|
||||
import { ReadOnlyNodePropertiesComponent } from "./ReadOnlyNodePropertiesComponent";
|
||||
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
|
||||
|
||||
export interface EditorNodePropertiesComponentProps {
|
||||
editedProperties: EditedProperties;
|
||||
@@ -48,7 +48,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
|
||||
const editedProperties = this.props.editedProperties;
|
||||
// search for it
|
||||
for (let i = 0; i < editedProperties.existingProperties.length; i++) {
|
||||
const ip = editedProperties.existingProperties[i];
|
||||
let ip = editedProperties.existingProperties[i];
|
||||
if (ip.key === key) {
|
||||
editedProperties.existingProperties.splice(i, 1);
|
||||
editedProperties.droppedKeys.push(key);
|
||||
@@ -60,7 +60,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
|
||||
|
||||
private removeAddedProperty(index: number): void {
|
||||
const editedProperties = this.props.editedProperties;
|
||||
const ap = editedProperties.addedProperties;
|
||||
let ap = editedProperties.addedProperties;
|
||||
ap.splice(index, 1);
|
||||
|
||||
this.props.onUpdateProperties(editedProperties);
|
||||
@@ -68,7 +68,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
|
||||
|
||||
private addProperty(): void {
|
||||
const editedProperties = this.props.editedProperties;
|
||||
const ap = editedProperties.addedProperties;
|
||||
let ap = editedProperties.addedProperties;
|
||||
ap.push({ key: "", values: [{ value: "", type: EditorNodePropertiesComponent.DEFAULT_PROPERTY_TYPE }] });
|
||||
this.props.onUpdateProperties(editedProperties);
|
||||
}
|
||||
@@ -126,7 +126,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
|
||||
onChange={(e) => {
|
||||
singleValue.type = e.target.value as ViewModels.InputPropertyValueTypeString;
|
||||
if (singleValue.type === "null") {
|
||||
singleValue.value = undefined;
|
||||
singleValue.value = null;
|
||||
}
|
||||
this.props.onUpdateProperties(this.props.editedProperties);
|
||||
}}
|
||||
@@ -144,7 +144,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
|
||||
className="rightPaneTrashIcon rightPaneBtns"
|
||||
as="span"
|
||||
aria-label="Delete property"
|
||||
onActivated={() => this.removeExistingProperty(key)}
|
||||
onActivated={(e) => this.removeExistingProperty(key)}
|
||||
>
|
||||
<img src={DeleteIcon} alt="Delete" />
|
||||
</AccessibleElement>
|
||||
@@ -166,7 +166,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
|
||||
className="rightPaneTrashIcon rightPaneBtns"
|
||||
as="span"
|
||||
aria-label="Remove existing property"
|
||||
onActivated={() => this.removeExistingProperty(nodeProp.key)}
|
||||
onActivated={(e) => this.removeExistingProperty(nodeProp.key)}
|
||||
>
|
||||
<img src={DeleteIcon} alt="Delete" />
|
||||
</AccessibleElement>
|
||||
@@ -206,7 +206,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
|
||||
onChange={(e) => {
|
||||
firstValue.value = e.target.value;
|
||||
if (firstValue.type === "null") {
|
||||
firstValue.value = undefined;
|
||||
firstValue.value = null;
|
||||
}
|
||||
this.props.onUpdateProperties(this.props.editedProperties);
|
||||
}}
|
||||
@@ -235,7 +235,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
|
||||
className="rightPaneTrashIcon rightPaneBtns"
|
||||
as="span"
|
||||
aria-label="Remove property"
|
||||
onActivated={() => this.removeAddedProperty(index)}
|
||||
onActivated={(e) => this.removeAddedProperty(index)}
|
||||
>
|
||||
<img src={DeleteIcon} alt="Delete" />
|
||||
</AccessibleElement>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||
import * as Q from "q";
|
||||
import * as React from "react";
|
||||
@@ -296,6 +294,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
this.setGremlinParams();
|
||||
}
|
||||
|
||||
const selectedNode = this.state.highlightedNode;
|
||||
|
||||
props.onGraphAccessorCreated({
|
||||
applyFilter: this.submitQuery.bind(this),
|
||||
addVertex: this.addVertex.bind(this),
|
||||
@@ -303,7 +303,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
});
|
||||
} // constructor
|
||||
|
||||
public shareIGraphConfig(igraphConfig: IGraphConfig): void {
|
||||
public shareIGraphConfig(igraphConfig: IGraphConfig) {
|
||||
this.setState({
|
||||
igraphConfig: { ...igraphConfig },
|
||||
});
|
||||
@@ -330,10 +330,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
const partitionKeyProperty = this.props.collectionPartitionKeyProperty;
|
||||
|
||||
// aggregate all the properties, remove dropped ones
|
||||
const finalProperties = editedProperties.existingProperties.concat(editedProperties.addedProperties);
|
||||
let finalProperties = editedProperties.existingProperties.concat(editedProperties.addedProperties);
|
||||
|
||||
// Compose the query
|
||||
const pkId = editedProperties.pkId;
|
||||
let pkId = editedProperties.pkId;
|
||||
let updateQueryFragment = "";
|
||||
|
||||
finalProperties.forEach((p) => {
|
||||
@@ -422,7 +422,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* Called from ko binding
|
||||
* @param id
|
||||
*/
|
||||
public selectNode(id: string): void {
|
||||
public selectNode(id: string) {
|
||||
if (!this.d3ForceGraph) {
|
||||
console.warn("Attempting to select node, but d3ForceGraph not initialized, yet.");
|
||||
return;
|
||||
@@ -431,7 +431,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
this.d3ForceGraph.selectNode(id);
|
||||
}
|
||||
|
||||
public deleteHighlightedNode(): void {
|
||||
public deleteHighlightedNode() {
|
||||
if (!this.state.highlightedNode) {
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, "No highlighted node to remove.");
|
||||
return;
|
||||
@@ -467,23 +467,23 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* Is of type: {e: GremlinEdge, v: GremlinVertex}[]
|
||||
* @param data
|
||||
*/
|
||||
public static isEdgeVertexPairArray(data: any): boolean {
|
||||
public static isEdgeVertexPairArray(data: any) {
|
||||
if (!(data instanceof Array)) {
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Info, "Query result not an array", data);
|
||||
return false;
|
||||
}
|
||||
|
||||
const pairs: any[] = data;
|
||||
let pairs: any[] = data;
|
||||
for (let i = 0; i < pairs.length; i++) {
|
||||
const item = pairs[i];
|
||||
if (
|
||||
!Object.prototype.hasOwnProperty.call(item, "e") ||
|
||||
!Object.prototype.hasOwnProperty.call(item, "v") ||
|
||||
!Object.prototype.hasOwnProperty.call(item["e"], "id") ||
|
||||
!Object.prototype.hasOwnProperty.call(item["e"], "type") ||
|
||||
!item.hasOwnProperty("e") ||
|
||||
!item.hasOwnProperty("v") ||
|
||||
!item["e"].hasOwnProperty("id") ||
|
||||
!item["e"].hasOwnProperty("type") ||
|
||||
item["e"].type !== "edge" ||
|
||||
!Object.prototype.hasOwnProperty.call(item["v"], "id") ||
|
||||
!Object.prototype.hasOwnProperty.call(item["e"], "type") ||
|
||||
!item["v"].hasOwnProperty("id") ||
|
||||
!item["v"].hasOwnProperty("type") ||
|
||||
item["v"].type !== "vertex"
|
||||
) {
|
||||
return false;
|
||||
@@ -514,7 +514,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
// Try hitting cache first
|
||||
const cache = outE ? this.outECache : this.inECache;
|
||||
const pairs = cache.retrieve(vertex.id, startIndex, pageSize);
|
||||
if (pairs !== null && pairs.length === pageSize) {
|
||||
if (pairs != null && pairs.length === pageSize) {
|
||||
const msg = `Retrieved ${pairs.length} ${outE ? "outE" : "inE"} edges from cache for vertex id: ${vertex.id}`;
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Info, msg);
|
||||
return Q.resolve(pairs);
|
||||
@@ -588,6 +588,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
vertex._outEAllLoaded &&
|
||||
vertex._inEAllLoaded
|
||||
) {
|
||||
console.info("No more edges to load for vertex " + vertex.id);
|
||||
updateGraphData();
|
||||
return Q.resolve(graphData);
|
||||
}
|
||||
@@ -667,7 +668,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
}
|
||||
);
|
||||
|
||||
return promise.then(() => {
|
||||
return promise.then((nbPairsFetched: number) => {
|
||||
if (offsetIndex >= GraphExplorer.LOAD_PAGE_SIZE || !vertex._outEAllLoaded || !vertex._inEAllLoaded) {
|
||||
vertex._pagination = {
|
||||
total:
|
||||
@@ -753,7 +754,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* Create a new edge in docdb and update graph
|
||||
* @param e
|
||||
*/
|
||||
public createNewEdge(e: GraphNewEdgeData): Q.Promise<unknown> {
|
||||
public createNewEdge(e: GraphNewEdgeData): Q.Promise<any> {
|
||||
const q = `g.V('${GraphUtil.escapeSingleQuotes(e.inputOutV)}').addE('${GraphUtil.escapeSingleQuotes(
|
||||
e.label
|
||||
)}').To(g.V('${GraphUtil.escapeSingleQuotes(e.inputInV)}'))`;
|
||||
@@ -771,8 +772,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
return;
|
||||
}
|
||||
|
||||
const edge = edges[0];
|
||||
const graphData = this.originalGraphData;
|
||||
let edge = edges[0];
|
||||
let graphData = this.originalGraphData;
|
||||
graphData.addEdge(edge);
|
||||
|
||||
// Allow loadNeighbors to load list new edge
|
||||
@@ -799,10 +800,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* Manually update in-memory graph.
|
||||
* @param edgeId
|
||||
*/
|
||||
public removeEdge(edgeId: string): Q.Promise<unknown> {
|
||||
public removeEdge(edgeId: string): Q.Promise<any> {
|
||||
return this.submitToBackend(`g.E('${GraphUtil.escapeSingleQuotes(edgeId)}').drop()`).then(
|
||||
() => {
|
||||
const graphData = this.originalGraphData;
|
||||
let graphData = this.originalGraphData;
|
||||
graphData.removeEdge(edgeId, false);
|
||||
this.updateGraphData(graphData, this.state.igraphConfig);
|
||||
},
|
||||
@@ -825,14 +826,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
return false;
|
||||
}
|
||||
|
||||
const vertices: any[] = data;
|
||||
let vertices: any[] = data;
|
||||
if (vertices.length > 0) {
|
||||
const v0 = vertices[0];
|
||||
if (
|
||||
!Object.prototype.hasOwnProperty.call(v0, "id") ||
|
||||
!Object.prototype.hasOwnProperty.call(v0, "type") ||
|
||||
v0.type !== "vertex"
|
||||
) {
|
||||
let v0 = vertices[0];
|
||||
if (!v0.hasOwnProperty("id") || !v0.hasOwnProperty("type") || v0.type !== "vertex") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -840,7 +837,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
}
|
||||
|
||||
public processGremlinQueryResults(result: GremlinClient.GremlinRequestResult): void {
|
||||
const data = result.data as GraphData.GremlinVertex[];
|
||||
const data = result.data as any;
|
||||
this.setFilterQueryStatus(FilterQueryStatus.GraphEmptyResult);
|
||||
|
||||
if (data === null) {
|
||||
@@ -930,13 +927,13 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
throw { title: err };
|
||||
}
|
||||
|
||||
if (vertices === null || vertices.length < 1) {
|
||||
if (vertices == null || vertices.length < 1) {
|
||||
const err = "Failed to create vertex (no vertex in response)";
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, err, vertices);
|
||||
throw { title: err };
|
||||
}
|
||||
|
||||
const vertex = vertices[0];
|
||||
let vertex = vertices[0];
|
||||
const graphData = this.originalGraphData;
|
||||
graphData.addVertex(vertex);
|
||||
this.updateGraphData(graphData, this.state.igraphConfig);
|
||||
@@ -1025,7 +1022,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
this.gremlinClient.destroy();
|
||||
}
|
||||
public componentDidMount(): void {
|
||||
if (this.props.onLoadStartKey !== null && this.props.onLoadStartKey !== undefined) {
|
||||
if (this.props.onLoadStartKey != null && this.props.onLoadStartKey != undefined) {
|
||||
TelemetryProcessor.traceSuccess(
|
||||
Action.Tab,
|
||||
{
|
||||
@@ -1085,7 +1082,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
public static reportToConsole(type: ConsoleDataType.Info, msg: string, ...errorData: any[]): void;
|
||||
public static reportToConsole(type: ConsoleDataType.Error, msg: string, ...errorData: any[]): void;
|
||||
public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) {
|
||||
let errorDataStr = "";
|
||||
let errorDataStr: string = "";
|
||||
if (errorData && errorData.length > 0) {
|
||||
console.error(msg, errorData);
|
||||
errorDataStr = ": " + JSON.stringify(errorData);
|
||||
@@ -1164,15 +1161,12 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
)}"`
|
||||
).then(
|
||||
(documents: DataModels.DocumentId[]) => {
|
||||
$.each(
|
||||
documents,
|
||||
(index: number, doc: { _graph_icon_property_value: string; icon: string; format: string }) => {
|
||||
newIconsMap[doc["_graph_icon_property_value"]] = {
|
||||
data: doc["icon"],
|
||||
format: doc["format"],
|
||||
};
|
||||
}
|
||||
);
|
||||
$.each(documents, (index: number, doc: any) => {
|
||||
newIconsMap[doc["_graph_icon_property_value"]] = {
|
||||
data: doc["icon"],
|
||||
format: doc["format"],
|
||||
};
|
||||
});
|
||||
|
||||
// Update graph configuration
|
||||
this.setState({
|
||||
@@ -1229,8 +1223,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
const key = this.state.igraphConfig.nodeCaption;
|
||||
return $.map(
|
||||
this.state.rootMap,
|
||||
(value: any): LeftPane.CaptionId => {
|
||||
const result = GraphData.GraphData.getNodePropValue(value, key);
|
||||
(value: any, index: number): LeftPane.CaptionId => {
|
||||
let result = GraphData.GraphData.getNodePropValue(value, key);
|
||||
return {
|
||||
caption: result !== undefined ? result : value.id,
|
||||
id: value.id,
|
||||
@@ -1243,7 +1237,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* Selecting a root node means
|
||||
* @param node
|
||||
*/
|
||||
private selectRootNode(id: string): Q.Promise<unknown> {
|
||||
private selectRootNode(id: string): Q.Promise<any> {
|
||||
if (!this.d3ForceGraph) {
|
||||
console.warn("Attempting to reset zoom, but d3ForceGraph not initialized, yet.");
|
||||
} else {
|
||||
@@ -1288,7 +1282,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
this.collectNodeProperties(this.originalGraphData.vertices);
|
||||
this.updatePropertiesPane(id);
|
||||
},
|
||||
(reason: string) => {
|
||||
(reason: any) => {
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, `Failed to select root node. Reason:${reason}`);
|
||||
}
|
||||
);
|
||||
@@ -1355,10 +1349,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
private getPkIdFromVertex(v: GraphData.GremlinVertex): string {
|
||||
if (
|
||||
this.props.collectionPartitionKeyProperty &&
|
||||
Object.prototype.hasOwnProperty.call(v, "properties") &&
|
||||
Object.prototype.hasOwnProperty.call(v.properties, this.props.collectionPartitionKeyProperty) &&
|
||||
v.hasOwnProperty("properties") &&
|
||||
v.properties.hasOwnProperty(this.props.collectionPartitionKeyProperty) &&
|
||||
v.properties[this.props.collectionPartitionKeyProperty].length > 0 &&
|
||||
Object.prototype.hasOwnProperty.call(v.properties[this.props.collectionPartitionKeyProperty][0], "value")
|
||||
v.properties[this.props.collectionPartitionKeyProperty][0].hasOwnProperty("value")
|
||||
) {
|
||||
const pk = v.properties[this.props.collectionPartitionKeyProperty][0].value;
|
||||
return GraphExplorer.generatePkIdPair(pk, v.id);
|
||||
@@ -1376,8 +1370,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
private getPkIdFromNodeData(v: GraphHighlightedNodeData): string {
|
||||
if (
|
||||
this.props.collectionPartitionKeyProperty &&
|
||||
Object.prototype.hasOwnProperty.call(v, "properties") &&
|
||||
Object.prototype.hasOwnProperty.call(v.properties, this.props.collectionPartitionKeyProperty)
|
||||
v.hasOwnProperty("properties") &&
|
||||
v.properties.hasOwnProperty(this.props.collectionPartitionKeyProperty)
|
||||
) {
|
||||
const pk = v.properties[this.props.collectionPartitionKeyProperty];
|
||||
return GraphExplorer.generatePkIdPair(pk[0] as PartitionKeyValueType, v.id);
|
||||
@@ -1394,14 +1388,14 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* @return id
|
||||
*/
|
||||
public static getPkIdFromDocumentId(d: DataModels.DocumentId, collectionPartitionKeyProperty: string): string {
|
||||
const { id } = d;
|
||||
let { id } = d;
|
||||
if (typeof id !== "string") {
|
||||
const error = `Vertex id is not a string: ${JSON.stringify(id)}.`;
|
||||
logConsoleError(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
if (collectionPartitionKeyProperty && Object.prototype.hasOwnProperty.call(d, collectionPartitionKeyProperty)) {
|
||||
if (collectionPartitionKeyProperty && d.hasOwnProperty(collectionPartitionKeyProperty)) {
|
||||
let pk = (d as any)[collectionPartitionKeyProperty];
|
||||
if (typeof pk !== "string" && typeof pk !== "number" && typeof pk !== "boolean") {
|
||||
if (Array.isArray(pk) && pk.length > 0) {
|
||||
@@ -1431,7 +1425,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
}"] AS p FROM c WHERE NOT IS_DEFINED(c._isEdge)`;
|
||||
return this.executeNonPagedDocDbQuery(q).then(
|
||||
(documents: DataModels.DocumentId[]) => {
|
||||
const possibleVertices = [] as PossibleVertex[];
|
||||
let possibleVertices = [] as PossibleVertex[];
|
||||
$.each(documents, (index: number, item: any) => {
|
||||
if (highlightedNodeId && item.id === highlightedNodeId) {
|
||||
// Exclude highlighed node in the list
|
||||
@@ -1445,7 +1439,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
caption: item.p,
|
||||
});
|
||||
} else {
|
||||
if (Object.prototype.hasOwnProperty.call(item, "p")) {
|
||||
if (item.hasOwnProperty("p")) {
|
||||
possibleVertices.push({
|
||||
value: item.id,
|
||||
caption: item.p[0]["_value"],
|
||||
@@ -1468,17 +1462,17 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* @param addedEdges
|
||||
* @return promise when done
|
||||
*/
|
||||
private editGraphEdges(editedEdges: EditedEdges): Q.Promise<unknown> {
|
||||
const promises = [];
|
||||
private editGraphEdges(editedEdges: EditedEdges): Q.Promise<any> {
|
||||
let promises = [];
|
||||
// Drop edges
|
||||
for (let i = 0; i < editedEdges.droppedIds.length; i++) {
|
||||
const id = editedEdges.droppedIds[i];
|
||||
let id = editedEdges.droppedIds[i];
|
||||
promises.push(this.removeEdge(id));
|
||||
}
|
||||
|
||||
// Add edges
|
||||
for (let i = 0; i < editedEdges.addedEdges.length; i++) {
|
||||
const e = editedEdges.addedEdges[i];
|
||||
let e = editedEdges.addedEdges[i];
|
||||
promises.push(
|
||||
this.createNewEdge(e).then(() => {
|
||||
// Reload neighbors in case we linked to a vertex that isn't loaded in the graph
|
||||
@@ -1531,9 +1525,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
/**
|
||||
* For unit testing purposes
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public onGraphUpdated(_timestamp: number): void {}
|
||||
public onGraphUpdated(timestamp: number): void {}
|
||||
|
||||
/**
|
||||
* Get node properties for styling purposes. Result is the union of all properties of all nodes.
|
||||
@@ -1541,17 +1533,17 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
private collectNodeProperties(vertices: GraphData.GremlinVertex[]) {
|
||||
const props = {} as any; // Hashset
|
||||
$.each(vertices, (index: number, item: GraphData.GremlinVertex) => {
|
||||
for (const p in item) {
|
||||
for (var p in item) {
|
||||
// DocDB: Exclude type because it's always 'vertex'
|
||||
if (p !== "type" && typeof (item as any)[p] === "string") {
|
||||
props[p] = true;
|
||||
}
|
||||
}
|
||||
// Inspect properties
|
||||
if (Object.prototype.hasOwnProperty.call(item, "properties")) {
|
||||
if (item.hasOwnProperty("properties")) {
|
||||
// TODO This is DocDB-graph specific
|
||||
// Assume each property value is [{value:... }]
|
||||
for (const f in item.properties) {
|
||||
for (var f in item.properties) {
|
||||
props[f] = true;
|
||||
}
|
||||
}
|
||||
@@ -1578,21 +1570,21 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
return;
|
||||
}
|
||||
|
||||
const data = this.originalGraphData.getVertexById(id);
|
||||
let data = this.originalGraphData.getVertexById(id);
|
||||
|
||||
// A bit of translation to make it easier to display
|
||||
const props: { [id: string]: ViewModels.GremlinPropertyValueType[] } = {};
|
||||
for (const p in data.properties) {
|
||||
let props: { [id: string]: ViewModels.GremlinPropertyValueType[] } = {};
|
||||
for (let p in data.properties) {
|
||||
props[p] = data.properties[p].map((gremlinProperty) => gremlinProperty.value);
|
||||
}
|
||||
|
||||
// update neighbors
|
||||
const sources: NeighborVertexBasicInfo[] = [];
|
||||
const targets: NeighborVertexBasicInfo[] = [];
|
||||
let sources: NeighborVertexBasicInfo[] = [];
|
||||
let targets: NeighborVertexBasicInfo[] = [];
|
||||
this.props.onResetDefaultGraphConfigValues();
|
||||
const nodeCaption = this.state.igraphConfigUiData.nodeCaptionChoice;
|
||||
let nodeCaption = this.state.igraphConfigUiData.nodeCaptionChoice;
|
||||
this.updateSelectedNodeNeighbors(data.id, nodeCaption, sources, targets);
|
||||
const sData: GraphHighlightedNodeData = {
|
||||
let sData: GraphHighlightedNodeData = {
|
||||
id: data.id,
|
||||
label: data.label,
|
||||
properties: props,
|
||||
@@ -1619,16 +1611,16 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
targets: NeighborVertexBasicInfo[]
|
||||
): void {
|
||||
// update neighbors
|
||||
const gd = this.originalGraphData;
|
||||
const v = gd.getVertexById(id);
|
||||
let gd = this.originalGraphData;
|
||||
let v = gd.getVertexById(id);
|
||||
|
||||
// Clear the array while keeping the references
|
||||
sources.length = 0;
|
||||
targets.length = 0;
|
||||
|
||||
const possibleEdgeLabels = {} as any; // Collect all edge labels in a hashset
|
||||
let possibleEdgeLabels = {} as any; // Collect all edge labels in a hashset
|
||||
|
||||
for (const p in v.inE) {
|
||||
for (let p in v.inE) {
|
||||
possibleEdgeLabels[p] = true;
|
||||
const edges = v.inE[p];
|
||||
$.each(edges, (index: number, edge: GraphData.GremlinShortInEdge) => {
|
||||
@@ -1637,7 +1629,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
|
||||
return;
|
||||
}
|
||||
const caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
||||
let caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
||||
sources.push({
|
||||
name: caption,
|
||||
id: neighborId,
|
||||
@@ -1647,7 +1639,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
});
|
||||
}
|
||||
|
||||
for (const p in v.outE) {
|
||||
for (let p in v.outE) {
|
||||
possibleEdgeLabels[p] = true;
|
||||
const edges = v.outE[p];
|
||||
$.each(edges, (index: number, edge: GraphData.GremlinShortOutEdge) => {
|
||||
@@ -1656,7 +1648,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
|
||||
return;
|
||||
}
|
||||
const caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
||||
let caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
||||
targets.push({
|
||||
name: caption,
|
||||
id: neighborId,
|
||||
@@ -1668,7 +1660,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
|
||||
this.setState({
|
||||
possibleEdgeLabels: Object.keys(possibleEdgeLabels).map(
|
||||
(value: string): InputTypeaheadComponent.Item => {
|
||||
(value: string, index: number, array: string[]): InputTypeaheadComponent.Item => {
|
||||
return { caption: value, value: value };
|
||||
}
|
||||
),
|
||||
@@ -1689,20 +1681,20 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedVertex = vertices[0];
|
||||
let updatedVertex = vertices[0];
|
||||
if (this.originalGraphData.hasVertexId(updatedVertex.id)) {
|
||||
const currentVertex = this.originalGraphData.getVertexById(updatedVertex.id);
|
||||
let currentVertex = this.originalGraphData.getVertexById(updatedVertex.id);
|
||||
// Copy updated properties
|
||||
if (Object.prototype.hasOwnProperty.call(currentVertex, "properties")) {
|
||||
if (currentVertex.hasOwnProperty("properties")) {
|
||||
delete currentVertex["properties"];
|
||||
}
|
||||
for (const p in updatedVertex) {
|
||||
for (var p in updatedVertex) {
|
||||
(currentVertex as any)[p] = updatedVertex[p];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO This kind of assumes saveVertexProperty is done from property panes.
|
||||
const hn = this.state.highlightedNode;
|
||||
let hn = this.state.highlightedNode;
|
||||
if (hn && hn.id === updatedVertex.id) {
|
||||
this.updatePropertiesPane(hn.id);
|
||||
}
|
||||
@@ -1716,7 +1708,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
igraphConfig?: IGraphConfig
|
||||
) {
|
||||
this.originalGraphData = graphData;
|
||||
const gd = JSON.parse(JSON.stringify(this.originalGraphData));
|
||||
let gd = JSON.parse(JSON.stringify(this.originalGraphData));
|
||||
if (!this.d3ForceGraph) {
|
||||
console.warn("Attempting to update graph, but d3ForceGraph not initialized, yet.");
|
||||
return;
|
||||
@@ -1881,7 +1873,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
|
||||
promise
|
||||
.then((result: GremlinClient.GremlinRequestResult) => this.processGremlinQueryResults(result))
|
||||
.catch((error: Error) => {
|
||||
.catch((error: any) => {
|
||||
const errorMsg = `Failed to process query result: ${getErrorMessage(error)}`;
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
||||
this.setState({
|
||||
|
||||
@@ -58,7 +58,7 @@ export class LeftPaneComponent extends React.Component<LeftPaneComponentProps> {
|
||||
className={className}
|
||||
as="tr"
|
||||
aria-label={node.caption}
|
||||
onActivated={() => this.props.onRootNodeSelected(node.id)}
|
||||
onActivated={(e) => this.props.onRootNodeSelected(node.id)}
|
||||
key={node.id}
|
||||
>
|
||||
<td className="resultItem">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as React from "react";
|
||||
import { GraphVizComponent, GraphVizComponentProps } from "./GraphVizComponent";
|
||||
import CollapseArrowIcon from "../../../../images/Collapse_arrow_14x14.svg";
|
||||
import ExpandIcon from "../../../../images/Expand_14x14.svg";
|
||||
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
|
||||
import { GraphVizComponent, GraphVizComponentProps } from "./GraphVizComponent";
|
||||
|
||||
interface MiddlePaneComponentProps {
|
||||
isTabsContentExpanded: boolean;
|
||||
@@ -17,14 +17,7 @@ export class MiddlePaneComponent extends React.Component<MiddlePaneComponentProp
|
||||
<div className="middlePane">
|
||||
<div className="graphTitle">
|
||||
<span className="paneTitle">Graph</span>
|
||||
<span
|
||||
className="graphExpandCollapseBtn pull-right"
|
||||
onClick={this.props.toggleExpandGraph}
|
||||
role="button"
|
||||
aria-expanded={this.props.isTabsContentExpanded}
|
||||
aria-name="View graph in full screen"
|
||||
tabIndex={0}
|
||||
>
|
||||
<span className="graphExpandCollapseBtn pull-right" onClick={this.props.toggleExpandGraph}>
|
||||
<img
|
||||
src={this.props.isTabsContentExpanded ? CollapseArrowIcon : ExpandIcon}
|
||||
alt={this.props.isTabsContentExpanded ? "collapse graph content" : "expand graph content"}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
import { mount, ReactWrapper } from "enzyme";
|
||||
import * as Q from "q";
|
||||
import React from "react";
|
||||
import { GraphHighlightedNodeData, PossibleVertex } from "./GraphExplorer";
|
||||
import { Mode, NodePropertiesComponent, NodePropertiesComponentProps } from "./NodePropertiesComponent";
|
||||
import { NodePropertiesComponent, NodePropertiesComponentProps, Mode } from "./NodePropertiesComponent";
|
||||
import { GraphHighlightedNodeData, EditedProperties, EditedEdges, PossibleVertex } from "./GraphExplorer";
|
||||
|
||||
describe("Property pane", () => {
|
||||
const title = "My Title";
|
||||
@@ -37,18 +37,17 @@ describe("Property pane", () => {
|
||||
return {
|
||||
expandedTitle: title,
|
||||
isCollapsed: false,
|
||||
onCollapsedChanged: jest.fn(),
|
||||
onCollapsedChanged: (newValue: boolean): void => {},
|
||||
node: highlightedNode,
|
||||
getPkIdFromNodeData: (): string => undefined,
|
||||
collectionPartitionKeyProperty: undefined,
|
||||
updateVertexProperties: (): Q.Promise<void> => Q.resolve(),
|
||||
selectNode: jest.fn(),
|
||||
updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(undefined),
|
||||
possibleEdgeLabels: undefined,
|
||||
//eslint-disable-next-line
|
||||
editGraphEdges: (): Q.Promise<any> => Q.resolve(),
|
||||
deleteHighlightedNode: jest.fn(),
|
||||
onModeChanged: jest.fn(),
|
||||
getPkIdFromNodeData: (v: GraphHighlightedNodeData): string => null,
|
||||
collectionPartitionKeyProperty: null,
|
||||
updateVertexProperties: (editedProperties: EditedProperties): Q.Promise<void> => Q.resolve(),
|
||||
selectNode: (id: string): void => {},
|
||||
updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(null),
|
||||
possibleEdgeLabels: null,
|
||||
editGraphEdges: (editedEdges: EditedEdges): Q.Promise<any> => Q.resolve(),
|
||||
deleteHighlightedNode: (): void => {},
|
||||
onModeChanged: (newMode: Mode): void => {},
|
||||
viewMode: Mode.READONLY_PROP,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
import * as React from "react";
|
||||
import CancelIcon from "../../../../images/cancel.svg";
|
||||
import CheckIcon from "../../../../images/check-1.svg";
|
||||
import CheckIcon from "../../../../images/check.svg";
|
||||
import DeleteIcon from "../../../../images/delete.svg";
|
||||
import EditIcon from "../../../../images/edit-1.svg";
|
||||
import EditIcon from "../../../../images/edit.svg";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
|
||||
import { CollapsiblePanel } from "../../Controls/CollapsiblePanel/CollapsiblePanel";
|
||||
@@ -72,7 +72,7 @@ export class NodePropertiesComponent extends React.Component<
|
||||
super(props);
|
||||
this.state = {
|
||||
editedProperties: {
|
||||
pkId: undefined,
|
||||
pkId: null,
|
||||
readOnlyProperties: [],
|
||||
existingProperties: [],
|
||||
addedProperties: [],
|
||||
@@ -98,12 +98,15 @@ export class NodePropertiesComponent extends React.Component<
|
||||
};
|
||||
}
|
||||
|
||||
public static getDerivedStateFromProps(props: NodePropertiesComponentProps): Partial<NodePropertiesComponentState> {
|
||||
public static getDerivedStateFromProps(
|
||||
props: NodePropertiesComponentProps,
|
||||
state: NodePropertiesComponentState
|
||||
): Partial<NodePropertiesComponentState> {
|
||||
if (props.viewMode !== Mode.READONLY_PROP) {
|
||||
return { isDeleteConfirm: false };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
@@ -135,10 +138,10 @@ export class NodePropertiesComponent extends React.Component<
|
||||
* @param value
|
||||
*/
|
||||
private static getTypeOption(value: any): ViewModels.InputPropertyValueTypeString {
|
||||
if (value === undefined) {
|
||||
if (value == null) {
|
||||
return "null";
|
||||
}
|
||||
const type = typeof value;
|
||||
let type = typeof value;
|
||||
switch (type) {
|
||||
case "number":
|
||||
case "boolean":
|
||||
@@ -169,9 +172,10 @@ export class NodePropertiesComponent extends React.Component<
|
||||
];
|
||||
|
||||
const existingProps: ViewModels.InputProperty[] = [];
|
||||
|
||||
if (this.props.node.hasOwnProperty("properties")) {
|
||||
const hProps = this.props.node["properties"];
|
||||
for (const p in hProps) {
|
||||
for (let p in hProps) {
|
||||
const propValues = hProps[p];
|
||||
(p === partitionKeyProperty ? readOnlyProps : existingProps).push({
|
||||
key: p,
|
||||
@@ -433,7 +437,7 @@ export class NodePropertiesComponent extends React.Component<
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
import CloseIcon from "../../../../images/close-black.svg";
|
||||
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
|
||||
import CloseIcon from "../../../../images/close-black.svg";
|
||||
|
||||
export interface QueryContainerComponentProps {
|
||||
initialQuery: string;
|
||||
@@ -82,7 +82,7 @@ export class QueryContainerComponent extends React.Component<
|
||||
<button
|
||||
type="button"
|
||||
className="filterbtnstyle queryButton"
|
||||
onClick={() => this.props.onExecuteClick(this.state.query)}
|
||||
onClick={(e) => this.props.onExecuteClick(this.state.query)}
|
||||
disabled={this.props.isLoading || !QueryContainerComponent.isQueryValid(this.state.query)}
|
||||
>
|
||||
Execute Gremlin Query
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
|
||||
import { GraphHighlightedNodeData, NeighborVertexBasicInfo } from "./GraphExplorer";
|
||||
import * as GraphUtil from "./GraphUtil";
|
||||
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
|
||||
|
||||
export interface ReadOnlyNeighborsComponentProps {
|
||||
node: GraphHighlightedNodeData;
|
||||
@@ -48,7 +48,7 @@ export class ReadOnlyNeighborsComponent extends React.Component<ReadOnlyNeighbor
|
||||
className="clickableLink"
|
||||
as="a"
|
||||
aria-label={_neighbor.name}
|
||||
onActivated={() => this.props.selectNode(_neighbor.id)}
|
||||
onActivated={(e) => this.props.selectNode(_neighbor.id)}
|
||||
title={GraphUtil.getNeighborTitle(_neighbor)}
|
||||
>
|
||||
{_neighbor.name}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { GraphHighlightedNodeData } from "./GraphExplorer";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
|
||||
export interface ReadOnlyNodePropertiesComponentProps {
|
||||
node: GraphHighlightedNodeData;
|
||||
|
||||
@@ -37,7 +37,7 @@ exports[`<EditorNodePropertiesComponent /> renders component 1`] = `
|
||||
</td>
|
||||
<td
|
||||
className="valueCol"
|
||||
title="efgh, 1234, true, false, undefined"
|
||||
title="efgh, 1234, true, false, undefined, null"
|
||||
>
|
||||
<div
|
||||
className="propertyValue"
|
||||
@@ -69,6 +69,12 @@ exports[`<EditorNodePropertiesComponent /> renders component 1`] = `
|
||||
>
|
||||
undefined
|
||||
</div>
|
||||
<div
|
||||
className="propertyValue isNull"
|
||||
key="null"
|
||||
>
|
||||
null
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
@@ -172,6 +178,12 @@ exports[`<EditorNodePropertiesComponent /> renders component 1`] = `
|
||||
>
|
||||
undefined
|
||||
</div>
|
||||
<div
|
||||
className="propertyValue isNull"
|
||||
key="null"
|
||||
>
|
||||
null
|
||||
</div>
|
||||
</td>
|
||||
<td />
|
||||
<td
|
||||
|
||||
@@ -8,11 +8,8 @@ import * as React from "react";
|
||||
import create, { UseStore } from "zustand";
|
||||
import { StyleConstants } from "../../../Common/Constants";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../../hooks/useTabs";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import Explorer from "../../Explorer";
|
||||
import { NotebookUtil } from "../../Notebook/NotebookUtil";
|
||||
import { useSelectedNode } from "../../useSelectedNode";
|
||||
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
||||
import * as CommandBarUtil from "./CommandBarUtil";
|
||||
@@ -56,16 +53,8 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
||||
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
||||
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
||||
|
||||
if (NotebookUtil.isPhoenixEnabled()) {
|
||||
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus(container, "connectionStatus"));
|
||||
}
|
||||
|
||||
if (
|
||||
userContext.features.phoenix === false &&
|
||||
userContext.features.notebooksTemporarilyDown === false &&
|
||||
useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2
|
||||
) {
|
||||
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
|
||||
if (container.tabsManager.activeTab()?.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
|
||||
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker", container.memoryUsageInfo));
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,8 +6,6 @@ import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
||||
import { updateUserContext } from "../../../UserContext";
|
||||
import Explorer from "../../Explorer";
|
||||
import NotebookManager from "../../Notebook/NotebookManager";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
import { useDatabases } from "../../useDatabases";
|
||||
import { useSelectedNode } from "../../useSelectedNode";
|
||||
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
||||
|
||||
@@ -29,6 +27,9 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
},
|
||||
} 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", () => {
|
||||
@@ -69,19 +70,18 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
},
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
updateUserContext({
|
||||
portalEnv: "prod",
|
||||
});
|
||||
useNotebook.getState().setIsNotebookEnabled(false);
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(false);
|
||||
});
|
||||
|
||||
it("Notebooks is already enabled - button should be hidden", () => {
|
||||
useNotebook.getState().setIsNotebookEnabled(true);
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
||||
@@ -89,6 +89,8 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
it("Account is running on one of the national clouds - button should be hidden", () => {
|
||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||
updateUserContext({
|
||||
portalEnv: "mooncake",
|
||||
});
|
||||
@@ -99,29 +101,27 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
it("Notebooks is not enabled but is available - button should be shown and enabled", () => {
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
||||
|
||||
//TODO: modify once notebooks are available
|
||||
expect(enableNotebookBtn).toBeUndefined();
|
||||
//expect(enableNotebookBtn).toBeDefined();
|
||||
//expect(enableNotebookBtn.disabled).toBe(false);
|
||||
//expect(enableNotebookBtn.tooltipText).toBe("");
|
||||
expect(enableNotebookBtn).toBeDefined();
|
||||
expect(enableNotebookBtn.disabled).toBe(false);
|
||||
expect(enableNotebookBtn.tooltipText).toBe("");
|
||||
});
|
||||
|
||||
it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => {
|
||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
||||
|
||||
//TODO: modify once notebooks are available
|
||||
expect(enableNotebookBtn).toBeUndefined();
|
||||
//expect(enableNotebookBtn).toBeDefined();
|
||||
//expect(enableNotebookBtn.disabled).toBe(true);
|
||||
//expect(enableNotebookBtn.tooltipText).toBe(
|
||||
// "Notebooks are not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."
|
||||
//);
|
||||
expect(enableNotebookBtn).toBeDefined();
|
||||
expect(enableNotebookBtn.disabled).toBe(true);
|
||||
expect(enableNotebookBtn.tooltipText).toBe(
|
||||
"Notebooks are not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -138,25 +138,24 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
},
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
mockExplorer.isShellEnabled = ko.observable(true);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
updateUserContext({
|
||||
apiType: "SQL",
|
||||
});
|
||||
useNotebook.getState().setIsShellEnabled(false);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
updateUserContext({
|
||||
apiType: "Mongo",
|
||||
});
|
||||
useNotebook.getState().setIsShellEnabled(true);
|
||||
});
|
||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||
|
||||
afterEach(() => {
|
||||
useNotebook.getState().setIsNotebookEnabled(false);
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(false);
|
||||
mockExplorer.isShellEnabled = ko.observable(true);
|
||||
});
|
||||
|
||||
it("Mongo Api not available - button should be hidden", () => {
|
||||
@@ -185,7 +184,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
it("Notebooks is not enabled and is available - button should be hidden", () => {
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||
@@ -193,36 +192,30 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
||||
useNotebook.getState().setIsNotebookEnabled(true);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||
expect(openMongoShellBtn).toBeDefined();
|
||||
|
||||
//TODO: modify once notebooks are available
|
||||
expect(openMongoShellBtn.disabled).toBe(true);
|
||||
//expect(openMongoShellBtn.disabled).toBe(false);
|
||||
//expect(openMongoShellBtn.tooltipText).toBe("");
|
||||
expect(openMongoShellBtn.disabled).toBe(false);
|
||||
expect(openMongoShellBtn.tooltipText).toBe("");
|
||||
});
|
||||
|
||||
it("Notebooks is enabled and is available - button should be shown and enabled", () => {
|
||||
useNotebook.getState().setIsNotebookEnabled(true);
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||
expect(openMongoShellBtn).toBeDefined();
|
||||
|
||||
//TODO: modify once notebooks are available
|
||||
expect(openMongoShellBtn.disabled).toBe(true);
|
||||
//expect(openMongoShellBtn.disabled).toBe(false);
|
||||
//expect(openMongoShellBtn.tooltipText).toBe("");
|
||||
expect(openMongoShellBtn.disabled).toBe(false);
|
||||
expect(openMongoShellBtn.tooltipText).toBe("");
|
||||
});
|
||||
|
||||
it("Notebooks is enabled and is available, terminal is unavailable due to ipRules - button should be hidden", () => {
|
||||
useNotebook.getState().setIsNotebookEnabled(true);
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
||||
useNotebook.getState().setIsShellEnabled(false);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||
mockExplorer.isShellEnabled = ko.observable(false);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||
@@ -243,6 +236,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
},
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -253,11 +247,8 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
},
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useNotebook.getState().setIsNotebookEnabled(false);
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(false);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||
});
|
||||
|
||||
it("Cassandra Api not available - button should be hidden", () => {
|
||||
@@ -268,6 +259,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
},
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
console.log(mockExplorer);
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||
expect(openCassandraShellBtn).toBeUndefined();
|
||||
@@ -290,7 +282,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
it("Notebooks is not enabled and is available - button should be shown and enabled", () => {
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||
@@ -298,31 +290,24 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
||||
useNotebook.getState().setIsNotebookEnabled(true);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||
|
||||
expect(openCassandraShellBtn).toBeDefined();
|
||||
|
||||
//TODO: modify once notebooks are available
|
||||
expect(openCassandraShellBtn.disabled).toBe(true);
|
||||
//expect(openCassandraShellBtn.disabled).toBe(false);
|
||||
//expect(openCassandraShellBtn.tooltipText).toBe("");
|
||||
expect(openCassandraShellBtn.disabled).toBe(false);
|
||||
expect(openCassandraShellBtn.tooltipText).toBe("");
|
||||
});
|
||||
|
||||
it("Notebooks is enabled and is available - button should be shown and enabled", () => {
|
||||
useNotebook.getState().setIsNotebookEnabled(true);
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||
expect(openCassandraShellBtn).toBeDefined();
|
||||
|
||||
//TODO: modify once notebooks are available
|
||||
expect(openCassandraShellBtn.disabled).toBe(true);
|
||||
//expect(openCassandraShellBtn.disabled).toBe(false);
|
||||
//expect(openCassandraShellBtn.tooltipText).toBe("");
|
||||
expect(openCassandraShellBtn.disabled).toBe(false);
|
||||
expect(openCassandraShellBtn.tooltipText).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -341,17 +326,23 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||
|
||||
mockExplorer.notebookManager = new NotebookManager();
|
||||
mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
useNotebook.getState().setIsNotebookEnabled(false);
|
||||
});
|
||||
|
||||
it("Notebooks is enabled and GitHubOAuthService is not logged in - connect to github button should be visible", () => {
|
||||
useNotebook.getState().setIsNotebookEnabled(true);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel);
|
||||
@@ -359,7 +350,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
it("Notebooks is enabled and GitHubOAuthService is logged in - manage github settings button should be visible", () => {
|
||||
useNotebook.getState().setIsNotebookEnabled(true);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||
mockExplorer.notebookManager.gitHubOAuthService.isLoggedIn = jest.fn().mockReturnValue(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
@@ -385,11 +376,10 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
describe("Resource token", () => {
|
||||
const mockCollection = { id: ko.observable("test") } as CollectionBase;
|
||||
useSelectedNode.getState().setSelectedNode(mockCollection);
|
||||
useDatabases.setState({ resourceTokenCollection: mockCollection });
|
||||
const selectedNodeState = useSelectedNode.getState();
|
||||
|
||||
beforeAll(() => {
|
||||
mockExplorer = {} as Explorer;
|
||||
mockExplorer.resourceTokenCollection = ko.observable(mockCollection);
|
||||
|
||||
updateUserContext({
|
||||
authType: AuthType.ResourceToken,
|
||||
|
||||
@@ -22,22 +22,15 @@ import * as Constants from "../../../Common/Constants";
|
||||
import { configContext, Platform } from "../../../ConfigContext";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { JunoClient } from "../../../Juno/JunoClient";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils";
|
||||
import { isServerlessAccount } from "../../../Utils/CapabilityUtils";
|
||||
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import Explorer from "../../Explorer";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
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 { SettingsPane } from "../../Panes/SettingsPane/SettingsPane";
|
||||
import { SetupNoteBooksPanel } from "../../Panes/SetupNotebooksPanel/SetupNotebooksPanel";
|
||||
import { useDatabases } from "../../useDatabases";
|
||||
import { SelectedNodeState } from "../../useSelectedNode";
|
||||
|
||||
let counter = 0;
|
||||
@@ -67,51 +60,34 @@ export function createStaticCommandBarButtons(
|
||||
newCollectionBtn.children.push(newDatabaseBtn);
|
||||
}
|
||||
|
||||
if (useNotebook.getState().isNotebookEnabled) {
|
||||
buttons.push(createDivider());
|
||||
const notebookButtons: CommandButtonComponentProps[] = [];
|
||||
buttons.push(createDivider());
|
||||
|
||||
if (container.isNotebookEnabled()) {
|
||||
const newNotebookButton = createNewNotebookButton(container);
|
||||
newNotebookButton.children = [createNewNotebookButton(container), createuploadNotebookButton(container)];
|
||||
notebookButtons.push(newNotebookButton);
|
||||
buttons.push(newNotebookButton);
|
||||
|
||||
if (container.notebookManager?.gitHubOAuthService) {
|
||||
notebookButtons.push(createManageGitHubAccountButton(container));
|
||||
buttons.push(createManageGitHubAccountButton(container));
|
||||
}
|
||||
|
||||
notebookButtons.push(createOpenTerminalButton(container));
|
||||
if (userContext.features.phoenix === false) {
|
||||
notebookButtons.push(createNotebookWorkspaceResetButton(container));
|
||||
}
|
||||
if (
|
||||
(userContext.apiType === "Mongo" &&
|
||||
useNotebook.getState().isShellEnabled &&
|
||||
selectedNodeState.isDatabaseNodeOrNoneSelected()) ||
|
||||
userContext.apiType === "Cassandra"
|
||||
) {
|
||||
notebookButtons.push(createDivider());
|
||||
if (userContext.apiType === "Cassandra") {
|
||||
notebookButtons.push(createOpenCassandraTerminalButton(container));
|
||||
} else {
|
||||
notebookButtons.push(createOpenMongoTerminalButton(container));
|
||||
}
|
||||
}
|
||||
buttons.push(createOpenTerminalButton(container));
|
||||
|
||||
notebookButtons.forEach((btn) => {
|
||||
if (userContext.features.notebooksTemporarilyDown) {
|
||||
if (btn.commandButtonLabel.indexOf("Cassandra") !== -1) {
|
||||
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.cassandraShellTemporarilyDownMsg);
|
||||
} else if (btn.commandButtonLabel.indexOf("Mongo") !== -1) {
|
||||
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.mongoShellTemporarilyDownMsg);
|
||||
} else {
|
||||
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg);
|
||||
}
|
||||
}
|
||||
buttons.push(btn);
|
||||
});
|
||||
buttons.push(createNotebookWorkspaceResetButton(container));
|
||||
|
||||
buttons.push(createDivider());
|
||||
buttons.push(createOpenPostgreSQLTerminalButton(container));
|
||||
|
||||
if (userContext.apiType === "Mongo" &&
|
||||
container.isShellEnabled() &&
|
||||
selectedNodeState.isDatabaseNodeOrNoneSelected()){
|
||||
buttons.push(createOpenMongoTerminalButton(container));
|
||||
}
|
||||
if (userContext.apiType === "Cassandra") {
|
||||
buttons.push(createOpenCassandraTerminalButton(container));
|
||||
}
|
||||
} else {
|
||||
if (!isRunningOnNationalCloud() && !userContext.features.notebooksTemporarilyDown) {
|
||||
buttons.push(createDivider());
|
||||
if (!isRunningOnNationalCloud()) {
|
||||
buttons.push(createEnableNotebooksButton(container));
|
||||
}
|
||||
}
|
||||
@@ -161,16 +137,14 @@ export function createContextCommandBarButtons(
|
||||
const buttons: CommandButtonComponentProps[] = [];
|
||||
|
||||
if (!selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") {
|
||||
const label = useNotebook.getState().isShellEnabled ? "Open Mongo Shell" : "New Shell";
|
||||
const label = container.isShellEnabled() ? "Open Mongo Shell" : "New Shell";
|
||||
const newMongoShellBtn: CommandButtonComponentProps = {
|
||||
iconSrc: HostedTerminalIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||
if (useNotebook.getState().isShellEnabled) {
|
||||
if (!userContext.features.notebooksTemporarilyDown) {
|
||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||
}
|
||||
if (container.isShellEnabled()) {
|
||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||
} else {
|
||||
selectedCollection && selectedCollection.onNewMongoShellClick();
|
||||
}
|
||||
@@ -178,13 +152,7 @@ export function createContextCommandBarButtons(
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
tooltipText:
|
||||
useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown
|
||||
? Constants.Notebook.mongoShellTemporarilyDownMsg
|
||||
: undefined,
|
||||
disabled:
|
||||
(selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") ||
|
||||
(useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown),
|
||||
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo",
|
||||
};
|
||||
buttons.push(newMongoShellBtn);
|
||||
}
|
||||
@@ -300,7 +268,7 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
|
||||
onCommandClick: () => container.openEnableSynapseLinkDialog(),
|
||||
commandButtonLabel: label,
|
||||
hasPopup: false,
|
||||
disabled: useNotebook.getState().isSynapseLinkUpdating,
|
||||
disabled: container.isSynapseLinkUpdating(),
|
||||
ariaLabel: label,
|
||||
};
|
||||
}
|
||||
@@ -310,8 +278,9 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps {
|
||||
return {
|
||||
iconSrc: AddDatabaseIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () =>
|
||||
useSidePanel.getState().openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={container} />),
|
||||
onCommandClick: () => {
|
||||
container.openAddDatabasePane();
|
||||
},
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
@@ -412,13 +381,6 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState)
|
||||
return buttons;
|
||||
}
|
||||
|
||||
function applyNotebooksTemporarilyDownStyle(buttonProps: CommandButtonComponentProps, tooltip: string): void {
|
||||
if (!buttonProps.isDivider) {
|
||||
buttonProps.disabled = true;
|
||||
buttonProps.tooltipText = tooltip;
|
||||
}
|
||||
}
|
||||
|
||||
function createNewNotebookButton(container: Explorer): CommandButtonComponentProps {
|
||||
const label = "New Notebook";
|
||||
return {
|
||||
@@ -450,8 +412,7 @@ function createOpenQueryButton(container: Explorer): CommandButtonComponentProps
|
||||
return {
|
||||
iconSrc: BrowseQueriesIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () =>
|
||||
useSidePanel.getState().openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={container} />),
|
||||
onCommandClick: () => container.openBrowseQueriesPanel(),
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
@@ -484,18 +445,12 @@ function createEnableNotebooksButton(container: Explorer): CommandButtonComponen
|
||||
return {
|
||||
iconSrc: EnableNotebooksIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
label,
|
||||
<SetupNoteBooksPanel explorer={container} panelTitle={label} panelDescription={description} />
|
||||
),
|
||||
onCommandClick: () => container.openSetupNotebooksPanel(label, description),
|
||||
commandButtonLabel: label,
|
||||
hasPopup: false,
|
||||
disabled: !useNotebook.getState().isNotebooksEnabledForAccount,
|
||||
disabled: !container.isNotebooksEnabledForAccount(),
|
||||
ariaLabel: label,
|
||||
tooltipText: useNotebook.getState().isNotebooksEnabledForAccount ? "" : tooltip,
|
||||
tooltipText: container.isNotebooksEnabledForAccount() ? "" : tooltip,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -512,6 +467,19 @@ function createOpenTerminalButton(container: Explorer): CommandButtonComponentPr
|
||||
};
|
||||
}
|
||||
|
||||
function createOpenPostgreSQLTerminalButton(container: Explorer): CommandButtonComponentProps {
|
||||
const label = "Open PostgreSQL Shell";
|
||||
return {
|
||||
iconSrc: HostedTerminalIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => container.openNotebookTerminal(ViewModels.TerminalKind.PostgreSQL),
|
||||
commandButtonLabel: label,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
ariaLabel: label,
|
||||
};
|
||||
}
|
||||
|
||||
function createOpenMongoTerminalButton(container: Explorer): CommandButtonComponentProps {
|
||||
const label = "Open Mongo Shell";
|
||||
const tooltip =
|
||||
@@ -519,21 +487,15 @@ function createOpenMongoTerminalButton(container: Explorer): CommandButtonCompon
|
||||
const title = "Set up workspace";
|
||||
const description =
|
||||
"Looks like you have not created a workspace for this account. To proceed and start using features including mongo shell and notebook, we will need to create a default workspace in this account.";
|
||||
const disableButton =
|
||||
!useNotebook.getState().isNotebooksEnabledForAccount && !useNotebook.getState().isNotebookEnabled;
|
||||
const disableButton = !container.isNotebooksEnabledForAccount() && !container.isNotebookEnabled();
|
||||
return {
|
||||
iconSrc: HostedTerminalIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => {
|
||||
if (useNotebook.getState().isNotebookEnabled) {
|
||||
if (container.isNotebookEnabled()) {
|
||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||
} else {
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
title,
|
||||
<SetupNoteBooksPanel explorer={container} panelTitle={title} panelDescription={description} />
|
||||
);
|
||||
container.openSetupNotebooksPanel(title, description);
|
||||
}
|
||||
},
|
||||
commandButtonLabel: label,
|
||||
@@ -551,21 +513,15 @@ function createOpenCassandraTerminalButton(container: Explorer): CommandButtonCo
|
||||
const title = "Set up workspace";
|
||||
const description =
|
||||
"Looks like you have not created a workspace for this account. To proceed and start using features including cassandra shell and notebook, we will need to create a default workspace in this account.";
|
||||
const disableButton =
|
||||
!useNotebook.getState().isNotebooksEnabledForAccount && !useNotebook.getState().isNotebookEnabled;
|
||||
const disableButton = !container.isNotebooksEnabledForAccount() && !container.isNotebookEnabled();
|
||||
return {
|
||||
iconSrc: HostedTerminalIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => {
|
||||
if (useNotebook.getState().isNotebookEnabled) {
|
||||
if (container.isNotebookEnabled()) {
|
||||
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
|
||||
} else {
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
title,
|
||||
<SetupNoteBooksPanel explorer={container} panelTitle={title} panelDescription={description} />
|
||||
);
|
||||
container.openSetupNotebooksPanel(title, description);
|
||||
}
|
||||
},
|
||||
commandButtonLabel: label,
|
||||
@@ -592,22 +548,10 @@ function createNotebookWorkspaceResetButton(container: Explorer): CommandButtonC
|
||||
function createManageGitHubAccountButton(container: Explorer): CommandButtonComponentProps {
|
||||
const connectedToGitHub: boolean = container.notebookManager?.gitHubOAuthService.isLoggedIn();
|
||||
const label = connectedToGitHub ? "Manage GitHub settings" : "Connect to GitHub";
|
||||
const junoClient = new JunoClient();
|
||||
return {
|
||||
iconSrc: GitHubIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => {
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
label,
|
||||
<GitHubReposPanel
|
||||
explorer={container}
|
||||
gitHubClientProp={container.notebookManager.gitHubClient}
|
||||
junoClientProp={junoClient}
|
||||
/>
|
||||
);
|
||||
},
|
||||
onCommandClick: () => container.openGitHubReposPanel(label),
|
||||
commandButtonLabel: label,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
@@ -622,12 +566,12 @@ function createStaticCommandBarButtonsForResourceToken(
|
||||
const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState);
|
||||
const openQueryBtn = createOpenQueryButton(container);
|
||||
|
||||
const resourceTokenCollection: ViewModels.CollectionBase = useDatabases.getState().resourceTokenCollection;
|
||||
const isResourceTokenCollectionNodeSelected: boolean =
|
||||
resourceTokenCollection?.id() === selectedNodeState.selectedNode?.id();
|
||||
container.resourceTokenCollection() &&
|
||||
container.resourceTokenCollection().id() === selectedNodeState.selectedNode?.id();
|
||||
newSqlQueryBtn.disabled = !isResourceTokenCollectionNodeSelected;
|
||||
newSqlQueryBtn.onCommandClick = () => {
|
||||
const resourceTokenCollection: ViewModels.CollectionBase = useDatabases.getState().resourceTokenCollection;
|
||||
const resourceTokenCollection: ViewModels.CollectionBase = container.resourceTokenCollection();
|
||||
resourceTokenCollection && resourceTokenCollection.onNewQueryClick(resourceTokenCollection, undefined);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,16 +6,16 @@ import {
|
||||
IDropdownOption,
|
||||
IDropdownStyles,
|
||||
} from "@fluentui/react";
|
||||
import { Observable } from "knockout";
|
||||
import * as React from "react";
|
||||
import _ from "underscore";
|
||||
import ChevronDownIcon from "../../../../images/Chevron_down.svg";
|
||||
import { StyleConstants } from "../../../Common/Constants";
|
||||
import { MemoryUsageInfo } from "../../../Contracts/DataModels";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import Explorer from "../../Explorer";
|
||||
import { ConnectionStatus } from "./ConnectionStatusComponent";
|
||||
import { MemoryTracker } from "./MemoryTrackerComponent";
|
||||
import { MemoryTrackerComponent } from "./MemoryTrackerComponent";
|
||||
|
||||
/**
|
||||
* Convert our NavbarButtonConfig to UI Fabric buttons
|
||||
@@ -24,13 +24,6 @@ import { MemoryTracker } from "./MemoryTrackerComponent";
|
||||
export const convertButton = (btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] => {
|
||||
const buttonHeightPx = StyleConstants.CommandBarButtonHeight;
|
||||
|
||||
const getFilter = (isDisabled: boolean): string => {
|
||||
if (isDisabled) {
|
||||
return StyleConstants.GrayScale;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return btns
|
||||
.filter((btn) => btn)
|
||||
.map(
|
||||
@@ -46,7 +39,6 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
|
||||
style: {
|
||||
width: StyleConstants.CommandBarIconWidth, // 16
|
||||
alignSelf: btn.iconName ? "baseline" : undefined,
|
||||
filter: getFilter(btn.disabled),
|
||||
},
|
||||
imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined,
|
||||
iconName: btn.iconName,
|
||||
@@ -133,12 +125,8 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
|
||||
width: 12,
|
||||
paddingLeft: 1,
|
||||
paddingTop: 6,
|
||||
filter: getFilter(btn.disabled),
|
||||
},
|
||||
imageProps: {
|
||||
src: ChevronDownIcon,
|
||||
alt: btn.iconAlt,
|
||||
},
|
||||
imageProps: { src: ChevronDownIcon, alt: btn.iconAlt },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -197,16 +185,12 @@ export const createDivider = (key: string): ICommandBarItemProps => {
|
||||
};
|
||||
};
|
||||
|
||||
export const createMemoryTracker = (key: string): ICommandBarItemProps => {
|
||||
export const createMemoryTracker = (
|
||||
key: string,
|
||||
memoryUsageInfo: Observable<MemoryUsageInfo>
|
||||
): ICommandBarItemProps => {
|
||||
return {
|
||||
key,
|
||||
onRender: () => <MemoryTracker />,
|
||||
};
|
||||
};
|
||||
|
||||
export const createConnectionStatus = (container: Explorer, key: string): ICommandBarItemProps => {
|
||||
return {
|
||||
key,
|
||||
onRender: () => <ConnectionStatus container={container} />,
|
||||
onRender: () => <MemoryTrackerComponent memoryUsageInfo={memoryUsageInfo} />,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
@import "../../../../less/Common/Constants";
|
||||
|
||||
.connectionStatusContainer {
|
||||
cursor: default;
|
||||
align-items: center;
|
||||
border: 1px;
|
||||
min-height: 44px;
|
||||
|
||||
> span {
|
||||
padding-right: 12px;
|
||||
font-size: 12px;
|
||||
font-family: @DataExplorerFont;
|
||||
color: @DefaultFontColor;
|
||||
}
|
||||
&:focus{
|
||||
outline: 0px;
|
||||
}
|
||||
}
|
||||
.commandReactBtn {
|
||||
&:hover {
|
||||
background-color: rgb(238, 247, 255);
|
||||
color: rgb(32, 31, 30);
|
||||
cursor: pointer;
|
||||
}
|
||||
&:focus{
|
||||
outline: 1px dashed #605e5c;
|
||||
}
|
||||
}
|
||||
.connectedReactBtn {
|
||||
&:hover {
|
||||
background-color: rgb(238, 247, 255);
|
||||
color: rgb(32, 31, 30);
|
||||
cursor: pointer;
|
||||
}
|
||||
&:focus{
|
||||
outline: 0px;
|
||||
}
|
||||
}
|
||||
.connectIcon{
|
||||
margin: 0px 4px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
color: rgb(0, 120, 212);
|
||||
}
|
||||
.status {
|
||||
position: relative;
|
||||
display: block;
|
||||
margin-right: 8px;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
font-size: 9px!important;
|
||||
padding: 0px!important;
|
||||
border-radius: 0.5em;
|
||||
}
|
||||
|
||||
.status::before,
|
||||
.status::after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.status::before {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
background-color: rgba(#fff, 0.1);
|
||||
border-radius: 100%;
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0) scale(0);
|
||||
}
|
||||
|
||||
.connected{
|
||||
background-color: green;
|
||||
box-shadow:
|
||||
0 0 0 0em rgba(green, 0),
|
||||
0em 0.05em 0.1em rgba(#000000, 0.2);
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
}
|
||||
.connecting{
|
||||
background-color:#ffbf00;
|
||||
box-shadow:
|
||||
0 0 0 0em rgba(#ffbf00, 0),
|
||||
0em 0.05em 0.1em rgba(#000000, 0.2);
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
}
|
||||
.failed{
|
||||
background-color:#bd1919;
|
||||
box-shadow:
|
||||
0 0 0 0em rgba(#bd1919, 0),
|
||||
0em 0.05em 0.1em rgba(#000000, 0.2);
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
}
|
||||
|
||||
.status.connecting.is-animating {
|
||||
animation: status-outer-connecting 3000ms infinite;
|
||||
}
|
||||
.status.failed.is-animating {
|
||||
animation: status-outer-failed 3000ms infinite;
|
||||
}
|
||||
.status.connected.is-animating {
|
||||
animation: status-outer-connected 3000ms infinite;
|
||||
}
|
||||
@keyframes status-outer-connected {
|
||||
|
||||
0% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em #008000, 0em 0.05em 0.1em rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
20% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0.6), 0em 0.05em 0.1em rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
40% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0.5), 0em 0.05em 0.1em rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
60% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0.3), 0em 0.05em 0.1em rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
80% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0.5em rgba(0, 128, 0, 0.1), 0em 0.05em 0.1em rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
85% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0), 0em 0.05em 0.1em rgba(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
@keyframes status-outer-failed {
|
||||
|
||||
0% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em #bd1919, 0em 0.05em 0.1em rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
20% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em #c52d2d, 0em 0.05em 0.1em rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
40% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em #b47b7b, 0em 0.05em 0.1em rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
60% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0.3), 0em 0.05em 0.1em rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
80% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0.5em rgba(0, 128, 0, 0.1), 0em 0.05em 0.1em rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
85% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0), 0em 0.05em 0.1em rgba(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
@keyframes status-outer-connecting {
|
||||
|
||||
0% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em #ffbf00, 0em 0.05em 0.1em rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
20% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em #f0dfad, 0em 0.05em 0.1em rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
40% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em rgba(198, 243, 198, 0.5), 0em 0.05em 0.1em rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
60% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em rgba(213, 241, 213, 0.3), 0em 0.05em 0.1em rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
80% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0.5em rgba(0, 128, 0, 0.1), 0em 0.05em 0.1em rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
85% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0), 0em 0.05em 0.1em rgba(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
import { Icon, ProgressIndicator, Stack, TooltipHost } from "@fluentui/react";
|
||||
import { ActionButton } from "@fluentui/react/lib/Button";
|
||||
import * as React from "react";
|
||||
import "../../../../less/hostedexplorer.less";
|
||||
import { ConnectionStatusType, Notebook } from "../../../Common/Constants";
|
||||
import Explorer from "../../Explorer";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
import "../CommandBar/ConnectionStatusComponent.less";
|
||||
interface Props {
|
||||
container: Explorer;
|
||||
}
|
||||
export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Element => {
|
||||
const [second, setSecond] = React.useState("00");
|
||||
const [minute, setMinute] = React.useState("00");
|
||||
const [isActive, setIsActive] = React.useState(false);
|
||||
const [counter, setCounter] = React.useState(0);
|
||||
const [statusColor, setStatusColor] = React.useState("");
|
||||
const [toolTipContent, setToolTipContent] = React.useState("Connect to temporary workspace.");
|
||||
React.useEffect(() => {
|
||||
let intervalId: NodeJS.Timeout;
|
||||
|
||||
if (isActive) {
|
||||
intervalId = setInterval(() => {
|
||||
const secondCounter = counter % 60;
|
||||
const minuteCounter = Math.floor(counter / 60);
|
||||
const computedSecond: string = String(secondCounter).length === 1 ? `0${secondCounter}` : `${secondCounter}`;
|
||||
const computedMinute: string = String(minuteCounter).length === 1 ? `0${minuteCounter}` : `${minuteCounter}`;
|
||||
|
||||
setSecond(computedSecond);
|
||||
setMinute(computedMinute);
|
||||
|
||||
setCounter((counter) => counter + 1);
|
||||
}, 1000);
|
||||
}
|
||||
return () => clearInterval(intervalId);
|
||||
}, [isActive, counter]);
|
||||
|
||||
const stopTimer = () => {
|
||||
setIsActive(false);
|
||||
setCounter(0);
|
||||
setSecond("00");
|
||||
setMinute("00");
|
||||
};
|
||||
|
||||
const connectionInfo = useNotebook((state) => state.connectionInfo);
|
||||
const memoryUsageInfo = useNotebook((state) => state.memoryUsageInfo);
|
||||
|
||||
const totalGB = memoryUsageInfo ? memoryUsageInfo.totalKB / Notebook.memoryGuageToGB : 0;
|
||||
const usedGB = totalGB > 0 ? totalGB - memoryUsageInfo.freeKB / Notebook.memoryGuageToGB : 0;
|
||||
|
||||
if (
|
||||
connectionInfo &&
|
||||
(connectionInfo.status === ConnectionStatusType.Connect || connectionInfo.status === ConnectionStatusType.ReConnect)
|
||||
) {
|
||||
return (
|
||||
<ActionButton className="commandReactBtn" onClick={() => container.allocateContainer()}>
|
||||
<TooltipHost content={toolTipContent}>
|
||||
<Stack className="connectionStatusContainer" horizontal>
|
||||
<Icon iconName="ConnectVirtualMachine" className="connectIcon" />
|
||||
<span>{connectionInfo.status}</span>
|
||||
</Stack>
|
||||
</TooltipHost>
|
||||
</ActionButton>
|
||||
);
|
||||
}
|
||||
|
||||
if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connecting && isActive === false) {
|
||||
setIsActive(true);
|
||||
setStatusColor("status connecting is-animating");
|
||||
setToolTipContent("Connecting to temporary workspace.");
|
||||
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connected && isActive === true) {
|
||||
stopTimer();
|
||||
setStatusColor("status connected is-animating");
|
||||
setToolTipContent("Connected to temporary workspace.");
|
||||
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Failed && isActive === true) {
|
||||
stopTimer();
|
||||
setStatusColor("status failed is-animating");
|
||||
setToolTipContent("Click here to Reconnect to temporary workspace.");
|
||||
}
|
||||
return (
|
||||
<ActionButton
|
||||
className={connectionInfo.status === ConnectionStatusType.Failed ? "commandReactBtn" : "connectedReactBtn"}
|
||||
onClick={(e: React.MouseEvent<HTMLSpanElement>) =>
|
||||
connectionInfo.status === ConnectionStatusType.Failed ? container.allocateContainer() : e.preventDefault()
|
||||
}
|
||||
>
|
||||
<TooltipHost content={toolTipContent}>
|
||||
<Stack className="connectionStatusContainer" horizontal>
|
||||
<i className={statusColor}></i>
|
||||
<span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}>
|
||||
{connectionInfo.status}
|
||||
</span>
|
||||
{connectionInfo.status === ConnectionStatusType.Connecting && isActive && (
|
||||
<ProgressIndicator description={minute + ":" + second} />
|
||||
)}
|
||||
{connectionInfo.status === ConnectionStatusType.Connected && !isActive && (
|
||||
<ProgressIndicator
|
||||
className={usedGB / totalGB > 0.8 ? "lowMemory" : ""}
|
||||
description={usedGB.toFixed(1) + " of " + totalGB.toFixed(1) + " GB"}
|
||||
percentComplete={usedGB / totalGB}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</TooltipHost>
|
||||
</ActionButton>
|
||||
);
|
||||
};
|
||||
@@ -1,29 +1,48 @@
|
||||
import { ProgressIndicator, Spinner, SpinnerSize, Stack } from "@fluentui/react";
|
||||
import { Observable, Subscription } from "knockout";
|
||||
import * as React from "react";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
import { MemoryUsageInfo } from "../../../Contracts/DataModels";
|
||||
|
||||
interface MemoryTrackerProps {
|
||||
memoryUsageInfo: Observable<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) {
|
||||
return (
|
||||
<Stack className="memoryTrackerContainer" horizontal>
|
||||
<span>Memory</span>
|
||||
<Spinner size={SpinnerSize.medium} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const totalGB = memoryUsageInfo.totalKB / 1048576;
|
||||
const usedGB = totalGB - memoryUsageInfo.freeKB / 1048576;
|
||||
|
||||
export const MemoryTracker: React.FC = (): JSX.Element => {
|
||||
const memoryUsageInfo = useNotebook((state) => state.memoryUsageInfo);
|
||||
if (!memoryUsageInfo) {
|
||||
return (
|
||||
<Stack className="memoryTrackerContainer" horizontal>
|
||||
<span>Memory</span>
|
||||
<Spinner size={SpinnerSize.medium} />
|
||||
<ProgressIndicator
|
||||
className={usedGB / totalGB > 0.8 ? "lowMemory" : ""}
|
||||
description={usedGB.toFixed(1) + " of " + totalGB.toFixed(1) + " GB"}
|
||||
percentComplete={usedGB / totalGB}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const totalGB = memoryUsageInfo.totalKB / 1048576;
|
||||
const usedGB = totalGB - memoryUsageInfo.freeKB / 1048576;
|
||||
|
||||
return (
|
||||
<Stack className="memoryTrackerContainer" horizontal>
|
||||
<span>Memory</span>
|
||||
<ProgressIndicator
|
||||
className={usedGB / totalGB > 0.8 ? "lowMemory" : ""}
|
||||
description={usedGB.toFixed(1) + " of " + totalGB.toFixed(1) + " GB"}
|
||||
percentComplete={usedGB / totalGB}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Dropdown, IDropdownOption } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import AnimateHeight from "react-animate-height";
|
||||
import LoaderIcon from "../../../../images/circular_loader_black_16x16.gif";
|
||||
import ClearIcon from "../../../../images/Clear-1.svg";
|
||||
import ClearIcon from "../../../../images/Clear.svg";
|
||||
import ErrorBlackIcon from "../../../../images/error_black.svg";
|
||||
import ErrorRedIcon from "../../../../images/error_red.svg";
|
||||
import infoBubbleIcon from "../../../../images/info-bubble-9x9.svg";
|
||||
@@ -129,7 +129,7 @@ export class NotificationConsoleComponent extends React.Component<
|
||||
className="expandCollapseButton"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={"console button" + (this.props.isConsoleExpanded ? " expanded" : " collapsed")}
|
||||
aria-label={"console button" + (this.props.isConsoleExpanded ? " collapsed" : " expanded")}
|
||||
aria-expanded={!this.props.isConsoleExpanded}
|
||||
>
|
||||
<img
|
||||
@@ -205,9 +205,7 @@ export class NotificationConsoleComponent extends React.Component<
|
||||
{item.type === ConsoleDataType.Error && <img className="errorIcon" src={ErrorRedIcon} alt="error" />}
|
||||
{item.type === ConsoleDataType.InProgress && <img className="loaderIcon" src={LoaderIcon} alt="in progress" />}
|
||||
<span className="date">{item.date}</span>
|
||||
<span className="message" role="alert" aria-live="assertive">
|
||||
{item.message}
|
||||
</span>
|
||||
<span className="message">{item.message}</span>
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
||||
</div>
|
||||
<div
|
||||
aria-expanded={true}
|
||||
aria-label="console button collapsed"
|
||||
aria-label="console button expanded"
|
||||
className="expandCollapseButton"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
@@ -236,7 +236,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
||||
</div>
|
||||
<div
|
||||
aria-expanded={true}
|
||||
aria-label="console button collapsed"
|
||||
aria-label="console button expanded"
|
||||
className="expandCollapseButton"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
@@ -340,9 +340,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
||||
date
|
||||
</span>
|
||||
<span
|
||||
aria-live="assertive"
|
||||
className="message"
|
||||
role="alert"
|
||||
>
|
||||
message
|
||||
</span>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { CellId, CellType, ImmutableNotebook } from "@nteract/commutable";
|
||||
// Vendor modules
|
||||
import {
|
||||
@@ -31,19 +30,6 @@ export interface NotebookComponentBootstrapperOptions {
|
||||
contentRef: ContentRef;
|
||||
}
|
||||
|
||||
interface IWrapModel {
|
||||
name: string;
|
||||
path: string;
|
||||
last_modified: Date;
|
||||
created: string;
|
||||
content: unknown;
|
||||
format: string;
|
||||
mimetype: unknown;
|
||||
size: number;
|
||||
writeable: boolean;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export class NotebookComponentBootstrapper {
|
||||
public contentRef: ContentRef;
|
||||
protected renderExtraComponent: () => JSX.Element;
|
||||
@@ -55,7 +41,7 @@ export class NotebookComponentBootstrapper {
|
||||
this.contentRef = options.contentRef;
|
||||
}
|
||||
|
||||
protected static wrapModelIntoContent(name: string, path: string, content: unknown): IWrapModel {
|
||||
protected static wrapModelIntoContent(name: string, path: string, content: any) {
|
||||
return {
|
||||
name,
|
||||
path,
|
||||
@@ -63,7 +49,7 @@ export class NotebookComponentBootstrapper {
|
||||
created: "",
|
||||
content,
|
||||
format: "json",
|
||||
mimetype: undefined,
|
||||
mimetype: null as any,
|
||||
size: 0,
|
||||
writeable: false,
|
||||
type: "notebook",
|
||||
@@ -99,7 +85,7 @@ export class NotebookComponentBootstrapper {
|
||||
};
|
||||
}
|
||||
|
||||
public setContent(name: string, content: unknown): void {
|
||||
public setContent(name: string, content: any): void {
|
||||
this.getStore().dispatch(
|
||||
actions.fetchContentFulfilled({
|
||||
filepath: undefined,
|
||||
@@ -284,16 +270,13 @@ export class NotebookComponentBootstrapper {
|
||||
public isContentDirty(): boolean {
|
||||
const content = selectors.content(this.getStore().getState(), { contentRef: this.contentRef });
|
||||
if (!content) {
|
||||
console.log("No error");
|
||||
return false;
|
||||
}
|
||||
|
||||
return selectors.notebook.isDirty(content.model as Immutable.RecordOf<DocumentRecordProps>);
|
||||
}
|
||||
|
||||
public isNotebookUntrusted(): boolean {
|
||||
return NotebookUtil.isNotebookUntrusted(this.getStore().getState(), this.contentRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* For display purposes, only return non-killed kernels
|
||||
*/
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { AppState, ContentRef, selectors } from "@nteract/core";
|
||||
import * as React from "react";
|
||||
import { AppState, ContentRef, selectors } from "@nteract/core";
|
||||
import { connect } from "react-redux";
|
||||
import { NotebookUtil } from "../NotebookUtil";
|
||||
import * as NteractUtil from "../NTeractUtil";
|
||||
|
||||
interface VirtualCommandBarComponentProps {
|
||||
kernelSpecName: string;
|
||||
kernelStatus: string;
|
||||
currentCellType: string;
|
||||
isNotebookUntrusted: boolean;
|
||||
onRender: () => void;
|
||||
}
|
||||
|
||||
@@ -22,8 +20,7 @@ class VirtualCommandBarComponent extends React.Component<VirtualCommandBarCompon
|
||||
return (
|
||||
this.props.kernelStatus !== nextProps.kernelStatus ||
|
||||
this.props.kernelSpecName !== nextProps.kernelSpecName ||
|
||||
this.props.currentCellType !== nextProps.currentCellType ||
|
||||
this.props.isNotebookUntrusted !== nextProps.isNotebookUntrusted
|
||||
this.props.currentCellType !== nextProps.currentCellType
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,7 +50,6 @@ const makeMapStateToProps = (
|
||||
kernelStatus,
|
||||
kernelSpecName,
|
||||
currentCellType,
|
||||
isNotebookUntrusted: NotebookUtil.isNotebookUntrusted(state, contentRef),
|
||||
} as VirtualCommandBarComponentProps;
|
||||
}
|
||||
|
||||
@@ -73,7 +69,6 @@ const makeMapStateToProps = (
|
||||
kernelStatus,
|
||||
kernelSpecName,
|
||||
currentCellType,
|
||||
isNotebookUntrusted: NotebookUtil.isNotebookUntrusted(state, contentRef),
|
||||
onRender: initialProps.onRender,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { AppState, ContentRef, selectors } from "@nteract/core";
|
||||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import styled from "styled-components";
|
||||
|
||||
import NotebookRenderer from "../../../NotebookRenderer/NotebookRenderer";
|
||||
import * as TextFile from "./text-file";
|
||||
|
||||
@@ -31,14 +32,14 @@ interface FileProps {
|
||||
|
||||
export class File extends React.PureComponent<FileProps> {
|
||||
getChoice = () => {
|
||||
let choice;
|
||||
let choice = null;
|
||||
|
||||
// notebooks don't report a mimetype so we'll use the content.type
|
||||
if (this.props.type === "notebook") {
|
||||
choice = <NotebookRenderer contentRef={this.props.contentRef} />;
|
||||
} else if (this.props.type === "dummy") {
|
||||
choice = undefined;
|
||||
} else if (this.props.mimetype === undefined || !TextFile.handles(this.props.mimetype)) {
|
||||
choice = null;
|
||||
} else if (this.props.mimetype == null || !TextFile.handles(this.props.mimetype)) {
|
||||
// This should not happen as we intercept mimetype upstream, but just in case
|
||||
choice = (
|
||||
<PaddedContainer>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import * as StringUtils from "../../../../../Utils/StringUtils";
|
||||
import { actions, AppState, ContentRef, selectors } from "@nteract/core";
|
||||
import { IMonacoProps as MonacoEditorProps } from "@nteract/monaco-editor";
|
||||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Dispatch } from "redux";
|
||||
import styled from "styled-components";
|
||||
import * as StringUtils from "../../../../../Utils/StringUtils";
|
||||
|
||||
const EditorContainer = styled.div`
|
||||
position: absolute;
|
||||
@@ -37,7 +37,7 @@ interface TextFileState {
|
||||
class EditorPlaceholder extends React.PureComponent<MonacoEditorProps> {
|
||||
render(): JSX.Element {
|
||||
// TODO: Show a little blocky placeholder
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ function makeMapStateToTextFileProps(
|
||||
|
||||
return {
|
||||
contentRef,
|
||||
mimetype: content.mimetype !== undefined ? content.mimetype : "text/plain",
|
||||
mimetype: content.mimetype != null ? content.mimetype : "text/plain",
|
||||
text,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
KernelRef,
|
||||
RemoteKernelProps,
|
||||
selectors,
|
||||
ServerConfig as JupyterServerConfig,
|
||||
ServerConfig as JupyterServerConfig
|
||||
} from "@nteract/core";
|
||||
import { Channels, childOf, createMessage, JupyterMessage, message, ofMessageType } from "@nteract/messaging";
|
||||
import { RecordOf } from "immutable";
|
||||
@@ -29,16 +29,14 @@ import {
|
||||
switchMap,
|
||||
take,
|
||||
tap,
|
||||
timeout,
|
||||
timeout
|
||||
} from "rxjs/operators";
|
||||
import { webSocket } from "rxjs/webSocket";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import { Areas } from "../../../Common/Constants";
|
||||
import { useTabs } from "../../../hooks/useTabs";
|
||||
import { Action as TelemetryAction, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { logConsoleError, logConsoleInfo } from "../../../Utils/NotificationConsoleUtils";
|
||||
import { useDialog } from "../../Controls/Dialog";
|
||||
import * as FileSystemUtil from "../FileSystemUtil";
|
||||
import * as cdbActions from "../NotebookComponent/actions";
|
||||
import { NotebookUtil } from "../NotebookUtil";
|
||||
@@ -687,8 +685,10 @@ const handleKernelConnectionLostEpic = (
|
||||
logConsoleError(msg);
|
||||
logFailureToTelemetry(state, "Kernel restart error", msg);
|
||||
|
||||
useDialog.getState().showOkModalDialog("kernel restarts", msg);
|
||||
|
||||
const explorer = window.dataExplorer;
|
||||
if (explorer) {
|
||||
explorer.showOkModalDialog("kernel restarts", msg);
|
||||
}
|
||||
return of(EMPTY);
|
||||
}
|
||||
|
||||
@@ -772,16 +772,15 @@ const closeUnsupportedMimetypesEpic = (
|
||||
ofType(actions.FETCH_CONTENT_FULFILLED),
|
||||
mergeMap((action) => {
|
||||
const mimetype = action.payload.model.mimetype;
|
||||
if (!TextFile.handles(mimetype)) {
|
||||
const explorer = window.dataExplorer;
|
||||
if (explorer && !TextFile.handles(mimetype)) {
|
||||
const filepath = action.payload.filepath;
|
||||
// Close tab and show error message
|
||||
useTabs
|
||||
.getState()
|
||||
.closeTabsByComparator(
|
||||
(tab: any) => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
|
||||
);
|
||||
explorer.tabsManager.closeTabsByComparator(
|
||||
(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.`;
|
||||
useDialog.getState().showOkModalDialog("File cannot be rendered", msg);
|
||||
explorer.showOkModalDialog("File cannot be rendered", msg);
|
||||
logConsoleError(msg);
|
||||
}
|
||||
return EMPTY;
|
||||
@@ -801,16 +800,17 @@ const closeContentFailedToFetchEpic = (
|
||||
return action$.pipe(
|
||||
ofType(actions.FETCH_CONTENT_FAILED),
|
||||
mergeMap((action) => {
|
||||
const filepath = action.payload.filepath;
|
||||
// Close tab and show error message
|
||||
useTabs
|
||||
.getState()
|
||||
.closeTabsByComparator(
|
||||
const explorer = window.dataExplorer;
|
||||
if (explorer) {
|
||||
const filepath = action.payload.filepath;
|
||||
// Close tab and show error message
|
||||
explorer.tabsManager.closeTabsByComparator(
|
||||
(tab: any) => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
|
||||
);
|
||||
const msg = `Failed to load file: ${filepath}.`;
|
||||
useDialog.getState().showOkModalDialog("Failure to load", msg);
|
||||
logConsoleError(msg);
|
||||
const msg = `Failed to load file: ${filepath}.`;
|
||||
explorer.showOkModalDialog("Failure to load", msg);
|
||||
logConsoleError(msg);
|
||||
}
|
||||
return EMPTY;
|
||||
})
|
||||
);
|
||||
|
||||
@@ -2,35 +2,30 @@
|
||||
* Notebook container related stuff
|
||||
*/
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { ConnectionStatusType } from "../../Common/Constants";
|
||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { ContainerConnectionInfo } from "../../Contracts/DataModels";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { createOrUpdate, destroy } from "../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { NotebookUtil } from "./NotebookUtil";
|
||||
import { useNotebook } from "./useNotebook";
|
||||
|
||||
export class NotebookContainerClient {
|
||||
private clearReconnectionAttemptMessage? = () => {};
|
||||
private isResettingWorkspace: boolean;
|
||||
|
||||
constructor(private onConnectionLost: () => void) {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (notebookServerInfo?.notebookServerEndpoint) {
|
||||
constructor(
|
||||
private notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>,
|
||||
private onConnectionLost: () => void,
|
||||
private onMemoryUsageInfoUpdate: (update: DataModels.MemoryUsageInfo) => void
|
||||
) {
|
||||
if (notebookServerInfo() && notebookServerInfo().notebookServerEndpoint) {
|
||||
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
|
||||
} else {
|
||||
const unsub = useNotebook.subscribe(
|
||||
(newServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => {
|
||||
if (newServerInfo?.notebookServerEndpoint) {
|
||||
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
|
||||
}
|
||||
unsub();
|
||||
},
|
||||
(state) => state.notebookServerInfo
|
||||
);
|
||||
const subscription = notebookServerInfo.subscribe((newServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => {
|
||||
if (newServerInfo && newServerInfo.notebookServerEndpoint) {
|
||||
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
|
||||
}
|
||||
subscription.dispose();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,14 +35,13 @@ export class NotebookContainerClient {
|
||||
private scheduleHeartbeat(delayMs: number): void {
|
||||
setTimeout(() => {
|
||||
this.getMemoryUsage()
|
||||
.then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo))
|
||||
.then((memoryUsageInfo) => this.onMemoryUsageInfoUpdate(memoryUsageInfo))
|
||||
.finally(() => this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs));
|
||||
}, delayMs);
|
||||
}
|
||||
|
||||
public async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
||||
private async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
|
||||
if (!this.notebookServerInfo() || !this.notebookServerInfo().notebookServerEndpoint) {
|
||||
const error = "No server endpoint detected";
|
||||
Logger.logError(error, "NotebookContainerClient/getMemoryUsage");
|
||||
return Promise.reject(error);
|
||||
@@ -78,12 +72,6 @@ export class NotebookContainerClient {
|
||||
freeKB: memoryUsageInfo.free,
|
||||
};
|
||||
}
|
||||
} else if (NotebookUtil.isPhoenixEnabled()) {
|
||||
const connectionStatus: ContainerConnectionInfo = {
|
||||
status: ConnectionStatusType.ReConnect,
|
||||
};
|
||||
useNotebook.getState().resetConatinerConnection(connectionStatus);
|
||||
useNotebook.getState().setIsRefreshed(true);
|
||||
}
|
||||
return undefined;
|
||||
} catch (error) {
|
||||
@@ -93,13 +81,6 @@ export class NotebookContainerClient {
|
||||
"Connection lost with Notebook server. Attempting to reconnect..."
|
||||
);
|
||||
}
|
||||
if (NotebookUtil.isPhoenixEnabled()) {
|
||||
const connectionStatus: ContainerConnectionInfo = {
|
||||
status: ConnectionStatusType.Failed,
|
||||
};
|
||||
useNotebook.getState().resetConatinerConnection(connectionStatus);
|
||||
useNotebook.getState().setIsRefreshed(true);
|
||||
}
|
||||
this.onConnectionLost();
|
||||
return undefined;
|
||||
}
|
||||
@@ -116,8 +97,7 @@ export class NotebookContainerClient {
|
||||
}
|
||||
|
||||
private async _resetWorkspace(): Promise<void> {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
||||
if (!this.notebookServerInfo() || !this.notebookServerInfo().notebookServerEndpoint) {
|
||||
const error = "No server endpoint detected";
|
||||
Logger.logError(error, "NotebookContainerClient/resetWorkspace");
|
||||
return Promise.reject(error);
|
||||
@@ -136,11 +116,15 @@ export class NotebookContainerClient {
|
||||
}
|
||||
|
||||
private getNotebookServerConfig(): { notebookServerEndpoint: string; authToken: string } {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
const authToken: string = notebookServerInfo.authToken ? `Token ${notebookServerInfo.authToken}` : undefined;
|
||||
let authToken: string,
|
||||
notebookServerEndpoint = this.notebookServerInfo().notebookServerEndpoint,
|
||||
token = this.notebookServerInfo().authToken;
|
||||
if (token) {
|
||||
authToken = `Token ${token}`;
|
||||
}
|
||||
|
||||
return {
|
||||
notebookServerEndpoint: notebookServerInfo.notebookServerEndpoint,
|
||||
notebookServerEndpoint,
|
||||
authToken,
|
||||
};
|
||||
}
|
||||
@@ -150,6 +134,7 @@ export class NotebookContainerClient {
|
||||
if (!databaseAccount?.id) {
|
||||
throw new Error("DataExplorer not initialized");
|
||||
}
|
||||
/*
|
||||
try {
|
||||
await destroy(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, "default");
|
||||
await createOrUpdate(
|
||||
@@ -162,5 +147,6 @@ export class NotebookContainerClient {
|
||||
Logger.logError(getErrorMessage(error), "NotebookContainerClient/recreateNotebookWorkspaceAsync");
|
||||
return Promise.reject(error);
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +1,44 @@
|
||||
import { stringifyNotebook } from "@nteract/commutable";
|
||||
import { FileType, IContent, IContentProvider, IEmptyContent, ServerConfig } from "@nteract/core";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { AjaxResponse } from "rxjs/ajax";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as StringUtils from "../../Utils/StringUtils";
|
||||
import * as FileSystemUtil from "./FileSystemUtil";
|
||||
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
||||
import { NotebookUtil } from "./NotebookUtil";
|
||||
import { useNotebook } from "./useNotebook";
|
||||
|
||||
export class NotebookContentClient {
|
||||
constructor(private contentProvider: IContentProvider) {}
|
||||
constructor(
|
||||
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
|
||||
* @param item
|
||||
*/
|
||||
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> {
|
||||
public updateItemChildren(item: NotebookContentItem): Promise<void> {
|
||||
return this.fetchNotebookFiles(item.path).then((subItems) => {
|
||||
item.children = subItems;
|
||||
subItems.forEach((subItem) => (subItem.parent = item));
|
||||
});
|
||||
}
|
||||
|
||||
private sleep = (milliseconds: number) => {
|
||||
return new Promise(resolve => setTimeout(resolve, milliseconds))
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param parent parent folder
|
||||
*/
|
||||
public async createNewNotebookFile(
|
||||
parent: NotebookContentItem,
|
||||
isGithubTree?: boolean
|
||||
): Promise<NotebookContentItem> {
|
||||
public createNewNotebookFile(parent: NotebookContentItem): Promise<NotebookContentItem> {
|
||||
if (!parent || parent.type !== NotebookContentItemType.Directory) {
|
||||
throw new Error(`Parent must be a directory: ${parent}`);
|
||||
}
|
||||
|
||||
const type = "notebook";
|
||||
|
||||
return this.contentProvider
|
||||
.create<"notebook">(this.getServerConfig(), parent.path, { type })
|
||||
.toPromise()
|
||||
@@ -60,8 +54,6 @@ export class NotebookContentClient {
|
||||
const notebookFile = xhr.response;
|
||||
|
||||
const item = NotebookUtil.createNotebookContentItem(notebookFile.name, notebookFile.path, notebookFile.type);
|
||||
useNotebook.getState().insertNotebookItem(parent, cloneDeep(item), isGithubTree);
|
||||
// TODO: delete when ResourceTreeAdapter is removed
|
||||
if (parent.children) {
|
||||
item.parent = parent;
|
||||
parent.children.push(item);
|
||||
@@ -71,20 +63,18 @@ export class NotebookContentClient {
|
||||
});
|
||||
}
|
||||
|
||||
public async deleteContentItem(item: NotebookContentItem, isGithubTree?: boolean): Promise<void> {
|
||||
const path = await this.deleteNotebookFile(item.path);
|
||||
useNotebook.getState().deleteNotebookItem(item, isGithubTree);
|
||||
public deleteContentItem(item: NotebookContentItem): Promise<void> {
|
||||
return this.deleteNotebookFile(item.path).then((path: string) => {
|
||||
if (!path || path !== item.path) {
|
||||
throw new Error("No path provided");
|
||||
}
|
||||
|
||||
// TODO: Delete once old resource tree is removed
|
||||
if (!path || path !== item.path) {
|
||||
throw new Error("No path provided");
|
||||
}
|
||||
|
||||
if (item.parent && item.parent.children) {
|
||||
// Remove deleted child
|
||||
const newChildren = item.parent.children.filter((child) => child.path !== path);
|
||||
item.parent.children = newChildren;
|
||||
}
|
||||
if (item.parent && item.parent.children) {
|
||||
// Remove deleted child
|
||||
const newChildren = item.parent.children.filter((child) => child.path !== path);
|
||||
item.parent.children = newChildren;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,12 +86,12 @@ export class NotebookContentClient {
|
||||
public async uploadFileAsync(
|
||||
name: string,
|
||||
content: string,
|
||||
parent: NotebookContentItem,
|
||||
isGithubTree?: boolean
|
||||
parent: NotebookContentItem
|
||||
): Promise<NotebookContentItem> {
|
||||
if (!parent || parent.type !== NotebookContentItemType.Directory) {
|
||||
throw new Error(`Parent must be a directory: ${parent}`);
|
||||
}
|
||||
|
||||
const filepath = NotebookUtil.getFilePath(parent.path, name);
|
||||
if (await this.checkIfFilepathExists(filepath)) {
|
||||
throw new Error(`File already exists: ${filepath}`);
|
||||
@@ -120,8 +110,6 @@ export class NotebookContentClient {
|
||||
.then((xhr: AjaxResponse) => {
|
||||
const notebookFile = xhr.response;
|
||||
const item = NotebookUtil.createNotebookContentItem(notebookFile.name, notebookFile.path, notebookFile.type);
|
||||
useNotebook.getState().insertNotebookItem(parent, cloneDeep(item), isGithubTree);
|
||||
// TODO: delete when ResourceTreeAdapter is removed
|
||||
if (parent.children) {
|
||||
item.parent = parent;
|
||||
parent.children.push(item);
|
||||
@@ -144,11 +132,7 @@ export class NotebookContentClient {
|
||||
* @param sourcePath
|
||||
* @param targetName is not prefixed with path
|
||||
*/
|
||||
public renameNotebook(
|
||||
item: NotebookContentItem,
|
||||
targetName: string,
|
||||
isGithubTree?: boolean
|
||||
): Promise<NotebookContentItem> {
|
||||
public renameNotebook(item: NotebookContentItem, targetName: string): Promise<NotebookContentItem> {
|
||||
const sourcePath = item.path;
|
||||
// Match extension
|
||||
if (sourcePath.indexOf(".") !== -1) {
|
||||
@@ -174,9 +158,6 @@ export class NotebookContentClient {
|
||||
item.name = notebookFile.name;
|
||||
item.path = notebookFile.path;
|
||||
item.timestamp = NotebookUtil.getCurrentTimestamp();
|
||||
|
||||
useNotebook.getState().updateNotebookItem(item, isGithubTree);
|
||||
|
||||
return item;
|
||||
});
|
||||
}
|
||||
@@ -186,11 +167,7 @@ export class NotebookContentClient {
|
||||
* @param parent
|
||||
* @param newDirectoryName basename of the new directory
|
||||
*/
|
||||
public async createDirectory(
|
||||
parent: NotebookContentItem,
|
||||
newDirectoryName: string,
|
||||
isGithubTree?: boolean
|
||||
): Promise<NotebookContentItem> {
|
||||
public async createDirectory(parent: NotebookContentItem, newDirectoryName: string): Promise<NotebookContentItem> {
|
||||
if (parent.type !== NotebookContentItemType.Directory) {
|
||||
throw new Error(`Parent is not a directory: ${parent.path}`);
|
||||
}
|
||||
@@ -217,11 +194,8 @@ export class NotebookContentClient {
|
||||
|
||||
const dir = xhr.response;
|
||||
const item = NotebookUtil.createNotebookContentItem(dir.name, dir.path, dir.type);
|
||||
useNotebook.getState().insertNotebookItem(parent, cloneDeep(item), isGithubTree);
|
||||
// TODO: delete when ResourceTreeAdapter is removed
|
||||
item.parent = parent;
|
||||
parent.children?.push(item);
|
||||
|
||||
return item;
|
||||
});
|
||||
}
|
||||
@@ -301,10 +275,9 @@ export class NotebookContentClient {
|
||||
}
|
||||
|
||||
private getServerConfig(): ServerConfig {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
return {
|
||||
endpoint: notebookServerInfo.notebookServerEndpoint,
|
||||
token: notebookServerInfo.authToken,
|
||||
endpoint: this.notebookServerInfo().notebookServerEndpoint,
|
||||
token: this.notebookServerInfo().authToken,
|
||||
crossDomain: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
|
||||
import { ImmutableNotebook } from "@nteract/commutable";
|
||||
import type { IContentProvider } from "@nteract/core";
|
||||
import ko from "knockout";
|
||||
import React from "react";
|
||||
import { contents } from "rx-jupyter";
|
||||
import { Areas, HttpStatusCodes } from "../../Common/Constants";
|
||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import { MemoryUsageInfo } from "../../Contracts/DataModels";
|
||||
import { GitHubClient } from "../../GitHub/GitHubClient";
|
||||
import { GitHubContentProvider } from "../../GitHub/GitHubContentProvider";
|
||||
import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService";
|
||||
@@ -18,10 +20,8 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { getFullName } from "../../Utils/UserUtils";
|
||||
import { useDialog } from "../Controls/Dialog";
|
||||
import Explorer from "../Explorer";
|
||||
import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane";
|
||||
import { GitHubReposPanel } from "../Panes/GitHubReposPanel/GitHubReposPanel";
|
||||
import { PublishNotebookPane } from "../Panes/PublishNotebookPane/PublishNotebookPane";
|
||||
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
|
||||
import { InMemoryContentProvider } from "./NotebookComponent/ContentProviders/InMemoryContentProvider";
|
||||
@@ -30,7 +30,6 @@ import { SnapshotRequest } from "./NotebookComponent/types";
|
||||
import { NotebookContainerClient } from "./NotebookContainerClient";
|
||||
import { NotebookContentClient } from "./NotebookContentClient";
|
||||
import { SchemaAnalyzerNotebook } from "./SchemaAnalyzer/SchemaAnalyzerUtils";
|
||||
import { useNotebook } from "./useNotebook";
|
||||
|
||||
type NotebookPaneContent = string | ImmutableNotebook;
|
||||
|
||||
@@ -38,6 +37,7 @@ export type { NotebookPaneContent };
|
||||
|
||||
export interface NotebookManagerOptions {
|
||||
container: Explorer;
|
||||
notebookBasePath: ko.Observable<string>;
|
||||
resourceTree: ResourceTreeAdapter;
|
||||
refreshCommandBarButtons: () => void;
|
||||
refreshNotebookList: () => void;
|
||||
@@ -81,28 +81,23 @@ export default class NotebookManager {
|
||||
contents.JupyterContentProvider
|
||||
);
|
||||
|
||||
this.notebookClient = new NotebookContainerClient(() =>
|
||||
this.params.container.initNotebooks(userContext?.databaseAccount)
|
||||
this.notebookClient = new NotebookContainerClient(
|
||||
this.params.container.notebookServerInfo,
|
||||
() => this.params.container.initNotebooks(userContext?.databaseAccount),
|
||||
(update: MemoryUsageInfo) => this.params.container.memoryUsageInfo(update)
|
||||
);
|
||||
|
||||
this.notebookContentClient = new NotebookContentClient(this.notebookContentProvider);
|
||||
this.notebookContentClient = new NotebookContentClient(
|
||||
this.params.container.notebookServerInfo,
|
||||
this.params.notebookBasePath,
|
||||
this.notebookContentProvider
|
||||
);
|
||||
|
||||
this.gitHubOAuthService.getTokenObservable().subscribe((token) => {
|
||||
this.gitHubClient.setToken(token?.access_token);
|
||||
if (this?.gitHubOAuthService.isLoggedIn()) {
|
||||
useSidePanel.getState().closeSidePanel();
|
||||
setTimeout(() => {
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"Manage GitHub settings",
|
||||
<GitHubReposPanel
|
||||
explorer={this.params.container}
|
||||
gitHubClientProp={this.params.container.notebookManager.gitHubClient}
|
||||
junoClientProp={this.junoClient}
|
||||
/>
|
||||
);
|
||||
}, 200);
|
||||
this.params.container.openGitHubReposPanel("Manager GitHub settings", this.junoClient);
|
||||
}
|
||||
|
||||
this.params.refreshCommandBarButtons();
|
||||
@@ -112,7 +107,6 @@ export default class NotebookManager {
|
||||
this.junoClient.subscribeToPinnedRepos((pinnedRepos) => {
|
||||
this.params.resourceTree.initializeGitHubRepos(pinnedRepos);
|
||||
this.params.resourceTree.triggerRender();
|
||||
useNotebook.getState().initializeGitHubRepos(pinnedRepos);
|
||||
});
|
||||
this.refreshPinnedRepos();
|
||||
}
|
||||
@@ -144,7 +138,6 @@ export default class NotebookManager {
|
||||
notebookContentRef={notebookContentRef}
|
||||
onTakeSnapshot={onTakeSnapshot}
|
||||
/>,
|
||||
"440px",
|
||||
onClosePanel
|
||||
);
|
||||
}
|
||||
@@ -173,33 +166,21 @@ export default class NotebookManager {
|
||||
if (error.status === HttpStatusCodes.Unauthorized) {
|
||||
this.gitHubOAuthService.resetToken();
|
||||
|
||||
useDialog
|
||||
.getState()
|
||||
.showOkCancelModalDialog(
|
||||
undefined,
|
||||
"Cosmos DB cannot access your Github account anymore. Please connect to GitHub again.",
|
||||
"Connect to GitHub",
|
||||
() =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"Connect to GitHub",
|
||||
<GitHubReposPanel
|
||||
explorer={this.params.container}
|
||||
gitHubClientProp={this.params.container.notebookManager.gitHubClient}
|
||||
junoClientProp={this.junoClient}
|
||||
/>
|
||||
),
|
||||
"Cancel",
|
||||
undefined
|
||||
);
|
||||
this.params.container.showOkCancelModalDialog(
|
||||
undefined,
|
||||
"Cosmos DB cannot access your Github account anymore. Please connect to GitHub again.",
|
||||
"Connect to GitHub",
|
||||
() => this.params.container.openGitHubReposPanel("Connect to GitHub"),
|
||||
"Cancel",
|
||||
undefined
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
private promptForCommitMsg = (title: string, primaryButtonLabel: string) => {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
let commitMsg = "Committed from Azure Cosmos DB Notebooks";
|
||||
useDialog.getState().showOkCancelModalDialog(
|
||||
this.params.container.showOkCancelModalDialog(
|
||||
title || "Commit",
|
||||
undefined,
|
||||
primaryButtonLabel || "Commit",
|
||||
@@ -212,7 +193,6 @@ export default class NotebookManager {
|
||||
"Cancel",
|
||||
() => reject(new Error("Commit dialog canceled")),
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
label: "Commit message",
|
||||
autoAdjustHeight: true,
|
||||
|
||||
@@ -16,10 +16,9 @@ import "./NotebookReadOnlyRenderer.less";
|
||||
import SandboxOutputs from "./outputs/SandboxOutputs";
|
||||
|
||||
export interface NotebookRendererProps {
|
||||
contentRef: ContentRef;
|
||||
contentRef: any;
|
||||
hideInputs?: boolean;
|
||||
hidePrompts?: boolean;
|
||||
addTransform: (component: React.ComponentType & { MIMETYPE: string }) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,7 +27,7 @@ export interface NotebookRendererProps {
|
||||
class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
||||
componentDidMount() {
|
||||
if (!userContext.features.sandboxNotebookOutputs) {
|
||||
loadTransform(this.props as NotebookRendererProps);
|
||||
loadTransform(this.props as any);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +59,7 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
||||
<div className="NotebookReadOnlyRender">
|
||||
<Cells contentRef={this.props.contentRef}>
|
||||
{{
|
||||
code: ({ id, contentRef }: { id: string; contentRef: ContentRef }) => (
|
||||
code: ({ id, contentRef }: { id: any; contentRef: ContentRef }) => (
|
||||
<CodeCell id={id} contentRef={contentRef}>
|
||||
{{
|
||||
prompt: (props: { id: string; contentRef: string }) => this.renderPrompt(props.id, props.contentRef),
|
||||
@@ -74,14 +73,14 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
||||
}}
|
||||
</CodeCell>
|
||||
),
|
||||
markdown: ({ id, contentRef }: { id: string; contentRef: ContentRef }) => (
|
||||
markdown: ({ id, contentRef }: { id: any; contentRef: ContentRef }) => (
|
||||
<MarkdownCell id={id} contentRef={contentRef} cell_type="markdown">
|
||||
{{
|
||||
editor: {},
|
||||
}}
|
||||
</MarkdownCell>
|
||||
),
|
||||
raw: ({ id, contentRef }: { id: string; contentRef: ContentRef }) => (
|
||||
raw: ({ id, contentRef }: { id: any; contentRef: ContentRef }) => (
|
||||
<RawCell id={id} contentRef={contentRef} cell_type="raw">
|
||||
{{
|
||||
editor: {
|
||||
@@ -99,7 +98,6 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: NotebookRendererProps) => {
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => {
|
||||
return {
|
||||
@@ -116,4 +114,4 @@ const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: Noteboo
|
||||
return mapDispatchToProps;
|
||||
};
|
||||
|
||||
export default connect(undefined, makeMapDispatchToProps)(NotebookReadOnlyRenderer);
|
||||
export default connect(null, makeMapDispatchToProps)(NotebookReadOnlyRenderer);
|
||||
|
||||
@@ -6,7 +6,7 @@ import CodeMirrorEditor from "@nteract/stateful-components/lib/inputs/connected-
|
||||
import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor";
|
||||
import * as React from "react";
|
||||
import { DndProvider } from "react-dnd";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
import HTML5Backend from "react-dnd-html5-backend";
|
||||
import { connect } from "react-redux";
|
||||
import { Dispatch } from "redux";
|
||||
import { userContext } from "../../../UserContext";
|
||||
@@ -14,7 +14,6 @@ import * as cdbActions from "../NotebookComponent/actions";
|
||||
import loadTransform from "../NotebookComponent/loadTransform";
|
||||
import { CdbAppState, SnapshotFragment, SnapshotRequest } from "../NotebookComponent/types";
|
||||
import { NotebookUtil } from "../NotebookUtil";
|
||||
import SecurityWarningBar from "../SecurityWarningBar/SecurityWarningBar";
|
||||
import { AzureTheme } from "./AzureTheme";
|
||||
import "./base.css";
|
||||
import CellCreator from "./decorators/CellCreator";
|
||||
@@ -108,7 +107,6 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
|
||||
return (
|
||||
<>
|
||||
<div className="NotebookRendererContainer">
|
||||
<SecurityWarningBar contentRef={this.props.contentRef} />
|
||||
<div className="NotebookRenderer" ref={this.notebookRendererRef}>
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<KeyboardShortcuts contentRef={this.props.contentRef}>
|
||||
|
||||
@@ -19,12 +19,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.disabledRunCellButton {
|
||||
.runCellButton .ms-Button-flexContainer .ms-Button-icon {
|
||||
color: @BaseMediumHigh;
|
||||
}
|
||||
}
|
||||
|
||||
.greyStopButton {
|
||||
.runCellButton .ms-Button-flexContainer .ms-Button-icon {
|
||||
color: @BaseMediumHigh;
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Dispatch } from "redux";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as cdbActions from "../NotebookComponent/actions";
|
||||
import { CdbAppState } from "../NotebookComponent/types";
|
||||
import { NotebookUtil } from "../NotebookUtil";
|
||||
|
||||
export interface PassedPromptProps {
|
||||
id: string;
|
||||
@@ -13,7 +12,6 @@ export interface PassedPromptProps {
|
||||
status?: string;
|
||||
executionCount?: number;
|
||||
isHovered?: boolean;
|
||||
isRunDisabled?: boolean;
|
||||
runCell?: () => void;
|
||||
stopCell?: () => void;
|
||||
}
|
||||
@@ -22,7 +20,6 @@ interface ComponentProps {
|
||||
id: string;
|
||||
contentRef: ContentRef;
|
||||
isHovered?: boolean;
|
||||
isNotebookUntrusted?: boolean;
|
||||
children: (props: PassedPromptProps) => React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -50,7 +47,6 @@ export class PromptPure extends React.Component<Props> {
|
||||
runCell: this.props.executeCell,
|
||||
stopCell: this.props.stopExecution,
|
||||
isHovered: this.props.isHovered,
|
||||
isRunDisabled: this.props.isNotebookUntrusted,
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
@@ -79,7 +75,6 @@ const makeMapStateToProps = (_state: CdbAppState, ownProps: ComponentProps): ((s
|
||||
status,
|
||||
executionCount,
|
||||
isHovered,
|
||||
isNotebookUntrusted: NotebookUtil.isNotebookUntrusted(state, contentRef),
|
||||
};
|
||||
};
|
||||
return mapStateToProps;
|
||||
|
||||