Compare commits

..

2 Commits

Author SHA1 Message Date
Srinath Narayanan
f637e685be Merge branch 'master' into users/srnara/selfServeDemo 2021-05-19 09:42:09 +05:30
Srinath Narayanan
c45c5868fb demo initial changes 2021-05-19 09:41:27 +05:30
407 changed files with 26847 additions and 13950 deletions

View File

@@ -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 DATA_EXPLORER_ENDPOINT=https://localhost:1234/hostedExplorer.html

View File

@@ -44,6 +44,7 @@ src/Definitions/png.d.ts
src/Definitions/svg.d.ts src/Definitions/svg.d.ts
src/Explorer/ComponentRegisterer.test.ts src/Explorer/ComponentRegisterer.test.ts
src/Explorer/ComponentRegisterer.ts src/Explorer/ComponentRegisterer.ts
src/Explorer/ContextMenuButtonFactory.ts
src/Explorer/Controls/CollapsiblePanel/CollapsiblePanelComponent.ts src/Explorer/Controls/CollapsiblePanel/CollapsiblePanelComponent.ts
src/Explorer/Controls/DiffEditor/DiffEditorComponent.ts src/Explorer/Controls/DiffEditor/DiffEditorComponent.ts
src/Explorer/Controls/DynamicList/DynamicList.test.ts src/Explorer/Controls/DynamicList/DynamicList.test.ts
@@ -71,6 +72,7 @@ src/Explorer/DataSamples/ContainerSampleGenerator.test.ts
src/Explorer/DataSamples/ContainerSampleGenerator.ts src/Explorer/DataSamples/ContainerSampleGenerator.ts
src/Explorer/DataSamples/DataSamplesUtil.test.ts src/Explorer/DataSamples/DataSamplesUtil.test.ts
src/Explorer/DataSamples/DataSamplesUtil.ts src/Explorer/DataSamples/DataSamplesUtil.ts
src/Explorer/Explorer.tsx
src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.test.ts src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.test.ts
src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.ts src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.ts
src/Explorer/Graph/GraphExplorerComponent/D3ForceGraph.test.ts src/Explorer/Graph/GraphExplorerComponent/D3ForceGraph.test.ts
@@ -82,6 +84,11 @@ src/Explorer/Graph/GraphExplorerComponent/GremlinClient.test.ts
src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts
src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.test.ts src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.test.ts
src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.ts src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.ts
# src/Explorer/Graph/GraphStyleComponent/GraphStyle.test.ts
# src/Explorer/Graph/GraphStyleComponent/GraphStyleComponent.ts
src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts
src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.ts
src/Explorer/Menus/ContextMenu.ts src/Explorer/Menus/ContextMenu.ts
src/Explorer/MostRecentActivity/MostRecentActivity.ts src/Explorer/MostRecentActivity/MostRecentActivity.ts
src/Explorer/Notebook/NotebookClientV2.ts src/Explorer/Notebook/NotebookClientV2.ts
@@ -98,11 +105,23 @@ src/Explorer/Notebook/NotebookContainerClient.ts
src/Explorer/Notebook/NotebookContentClient.ts src/Explorer/Notebook/NotebookContentClient.ts
src/Explorer/Notebook/NotebookContentItem.ts src/Explorer/Notebook/NotebookContentItem.ts
src/Explorer/Notebook/NotebookUtil.ts src/Explorer/Notebook/NotebookUtil.ts
src/Explorer/OpenActions.test.ts
src/Explorer/OpenActions.ts
src/Explorer/OpenActionsStubs.ts src/Explorer/OpenActionsStubs.ts
src/Explorer/Panes/AddDatabasePane.ts
src/Explorer/Panes/AddDatabasePane.test.ts
src/Explorer/Panes/BrowseQueriesPane.ts
src/Explorer/Panes/ContextualPaneBase.ts
# src/Explorer/Panes/GraphStylingPane.ts
# src/Explorer/Panes/NewVertexPane.ts
src/Explorer/Panes/RenewAdHocAccessPane.ts
src/Explorer/Panes/SetupNotebooksPane.ts
src/Explorer/Panes/SwitchDirectoryPane.ts
src/Explorer/Panes/Tables/Validators/EntityPropertyNameValidator.ts src/Explorer/Panes/Tables/Validators/EntityPropertyNameValidator.ts
src/Explorer/Panes/Tables/Validators/EntityPropertyValidationCommon.ts src/Explorer/Panes/Tables/Validators/EntityPropertyValidationCommon.ts
src/Explorer/Panes/Tables/Validators/EntityPropertyValueValidator.ts src/Explorer/Panes/Tables/Validators/EntityPropertyValueValidator.ts
src/Explorer/SplashScreen/SplashScreen.test.ts src/Explorer/SplashScreen/SplashScreen.test.ts
src/Explorer/Tables/Constants.ts
src/Explorer/Tables/DataTable/CacheBase.ts src/Explorer/Tables/DataTable/CacheBase.ts
src/Explorer/Tables/DataTable/DataTableBindingManager.ts src/Explorer/Tables/DataTable/DataTableBindingManager.ts
src/Explorer/Tables/DataTable/DataTableBuilder.ts src/Explorer/Tables/DataTable/DataTableBuilder.ts
@@ -119,6 +138,7 @@ src/Explorer/Tables/QueryBuilder/ClauseGroupViewModel.ts
src/Explorer/Tables/QueryBuilder/CustomTimestampHelper.ts src/Explorer/Tables/QueryBuilder/CustomTimestampHelper.ts
src/Explorer/Tables/QueryBuilder/QueryBuilderViewModel.ts src/Explorer/Tables/QueryBuilder/QueryBuilderViewModel.ts
src/Explorer/Tables/QueryBuilder/QueryClauseViewModel.ts src/Explorer/Tables/QueryBuilder/QueryClauseViewModel.ts
src/Explorer/Tables/QueryBuilder/QueryViewModel.ts
src/Explorer/Tables/TableDataClient.ts src/Explorer/Tables/TableDataClient.ts
src/Explorer/Tables/TableEntityProcessor.ts src/Explorer/Tables/TableEntityProcessor.ts
src/Explorer/Tables/Utilities.ts src/Explorer/Tables/Utilities.ts
@@ -128,67 +148,170 @@ src/Explorer/Tabs/DocumentsTab.test.ts
src/Explorer/Tabs/DocumentsTab.ts src/Explorer/Tabs/DocumentsTab.ts
src/Explorer/Tabs/GraphTab.ts src/Explorer/Tabs/GraphTab.ts
src/Explorer/Tabs/MongoDocumentsTab.ts src/Explorer/Tabs/MongoDocumentsTab.ts
src/Explorer/Tabs/MongoQueryTab.ts
src/Explorer/Tabs/MongoShellTab.ts
src/Explorer/Tabs/NotebookV2Tab.ts src/Explorer/Tabs/NotebookV2Tab.ts
src/Explorer/Tabs/QueryTab.test.ts
src/Explorer/Tabs/QueryTab.ts
src/Explorer/Tabs/QueryTablesTab.ts
src/Explorer/Tabs/ScriptTabBase.ts src/Explorer/Tabs/ScriptTabBase.ts
src/Explorer/Tabs/StoredProcedureTab.ts
src/Explorer/Tabs/TabComponents.ts src/Explorer/Tabs/TabComponents.ts
src/Explorer/Tabs/TabsBase.ts src/Explorer/Tabs/TabsBase.ts
src/Explorer/Tabs/TriggerTab.ts src/Explorer/Tabs/TriggerTab.ts
src/Explorer/Tabs/UserDefinedFunctionTab.ts src/Explorer/Tabs/UserDefinedFunctionTab.ts
src/Explorer/Tree/AccessibleVerticalList.ts src/Explorer/Tree/AccessibleVerticalList.ts
src/Explorer/Tree/Collection.test.ts
src/Explorer/Tree/Collection.ts src/Explorer/Tree/Collection.ts
src/Explorer/Tree/ConflictId.ts src/Explorer/Tree/ConflictId.ts
src/Explorer/Tree/Database.ts
src/Explorer/Tree/DocumentId.ts src/Explorer/Tree/DocumentId.ts
src/Explorer/Tree/ObjectId.ts src/Explorer/Tree/ObjectId.ts
src/Explorer/Tree/ResourceTokenCollection.ts src/Explorer/Tree/ResourceTokenCollection.ts
src/Explorer/Tree/StoredProcedure.ts src/Explorer/Tree/StoredProcedure.ts
src/Explorer/Tree/TreeComponents.ts src/Explorer/Tree/TreeComponents.ts
src/Explorer/Tree/Trigger.ts src/Explorer/Tree/Trigger.ts
src/Explorer/Tree/UserDefinedFunction.ts
src/Explorer/WaitsForTemplateViewModel.ts src/Explorer/WaitsForTemplateViewModel.ts
src/GitHub/GitHubClient.test.ts src/GitHub/GitHubClient.test.ts
src/GitHub/GitHubClient.ts src/GitHub/GitHubClient.ts
src/GitHub/GitHubConnector.ts src/GitHub/GitHubConnector.ts
src/GitHub/GitHubContentProvider.test.ts
src/GitHub/GitHubContentProvider.ts
src/GitHub/GitHubOAuthService.ts src/GitHub/GitHubOAuthService.ts
src/HostedExplorer.ts
src/Index.ts src/Index.ts
src/Juno/JunoClient.test.ts src/Juno/JunoClient.test.ts
src/Juno/JunoClient.ts src/Juno/JunoClient.ts
src/Main.ts
src/NotebookWorkspaceManager/NotebookWorkspaceManager.ts
src/NotebookWorkspaceManager/NotebookWorkspaceResourceProviderMockClients.ts
src/Platform/Emulator/DataAccessUtility.ts
src/Platform/Emulator/ExplorerFactory.ts
src/Platform/Emulator/Main.ts
src/Platform/Emulator/NotificationsClient.ts
src/Platform/Hosted/ArmResourceUtils.ts
src/Platform/Hosted/Authorization.ts src/Platform/Hosted/Authorization.ts
src/Platform/Hosted/DataAccessUtility.ts
src/Platform/Hosted/ExplorerFactory.ts
src/Platform/Hosted/Helpers/ConnectionStringParser.test.ts
src/Platform/Hosted/Main.ts
src/Platform/Hosted/Maint.test.ts
src/Platform/Hosted/NotificationsClient.ts
src/Platform/Portal/DataAccessUtility.ts
src/Platform/Portal/ExplorerFactory.ts
src/Platform/Portal/Main.ts
src/Platform/Portal/NotificationsClient.ts
src/PlatformType.ts
src/ReactDevTools.ts src/ReactDevTools.ts
src/ResourceProvider/IResourceProviderClient.test.ts
src/ResourceProvider/IResourceProviderClient.ts
src/ResourceProvider/ResourceProviderClient.ts
src/ResourceProvider/ResourceProviderClientFactory.ts
src/RouteHandlers/RouteHandler.ts
src/RouteHandlers/TabRouteHandler.test.ts
src/RouteHandlers/TabRouteHandler.ts
src/Shared/Constants.ts src/Shared/Constants.ts
src/Shared/DefaultExperienceUtility.test.ts src/Shared/DefaultExperienceUtility.test.ts
src/Shared/DefaultExperienceUtility.ts src/Shared/DefaultExperienceUtility.ts
src/Shared/ExplorerSettings.ts
src/Shared/PriceEstimateCalculator.ts
src/Shared/StorageUtility.test.ts
src/Shared/StorageUtility.ts
src/Shared/appInsights.ts src/Shared/appInsights.ts
src/SparkClusterManager/ArcadiaResourceManager.ts src/SparkClusterManager/ArcadiaResourceManager.ts
src/SparkClusterManager/SparkClusterManager.ts src/SparkClusterManager/SparkClusterManager.ts
src/Terminal/JupyterLabAppFactory.ts src/Terminal/JupyterLabAppFactory.ts
src/Terminal/NotebookAppContracts.d.ts src/Terminal/NotebookAppContracts.d.ts
src/Terminal/index.ts
src/TokenProviders/PortalTokenProvider.ts
src/TokenProviders/TokenProviderFactory.ts
src/Utils/PricingUtils.test.ts
src/Utils/QueryUtils.test.ts
src/applyExplorerBindings.ts src/applyExplorerBindings.ts
src/global.d.ts src/global.d.ts
src/setupTests.ts src/setupTests.ts
src/Explorer/Controls/AccessibleElement/AccessibleElement.tsx
src/Explorer/Controls/Accordion/AccordionComponent.tsx
src/Explorer/Controls/AccountSwitch/AccountSwitchComponent.test.tsx
src/Explorer/Controls/AccountSwitch/AccountSwitchComponent.tsx
src/Explorer/Controls/AccountSwitch/AccountSwitchComponentAdapter.tsx
src/Explorer/Controls/Arcadia/ArcadiaMenuPicker.tsx
src/Explorer/Controls/CollapsiblePanel/CollapsiblePanel.tsx
src/Explorer/Controls/CommandButton/CommandButtonComponent.tsx
src/Explorer/Controls/DialogReactComponent/DialogComponent.tsx
src/Explorer/Controls/DialogReactComponent/DialogComponentAdapter.tsx
src/Explorer/Controls/Directory/DefaultDirectoryDropdownComponent.test.tsx
src/Explorer/Controls/Directory/DefaultDirectoryDropdownComponent.tsx
src/Explorer/Controls/Directory/DirectoryComponentAdapter.tsx
src/Explorer/Controls/Directory/DirectoryListComponent.test.tsx
src/Explorer/Controls/Directory/DirectoryListComponent.tsx
src/Explorer/Controls/Editor/EditorReact.tsx
src/Explorer/Controls/InputTypeahead/InputTypeaheadComponent.tsx src/Explorer/Controls/InputTypeahead/InputTypeaheadComponent.tsx
src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx
src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.tsx
src/NotebookViewer/NotebookViewer.tsx
src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
src/Explorer/Controls/QueriesGridReactComponent/QueriesGridComponent.tsx
src/Explorer/Controls/QueriesGridReactComponent/QueriesGridComponentAdapter.tsx
src/Explorer/Controls/ResizeSensorReactComponent/ResizeSensorComponent.tsx
src/Explorer/Controls/Spark/ClusterSettingsComponent.tsx
src/Explorer/Controls/Spark/ClusterSettingsComponentAdapter.tsx
src/Explorer/Controls/Tabs/TabComponent.tsx
src/Explorer/Controls/TreeComponent/TreeComponent.test.tsx
src/Explorer/Controls/TreeComponent/TreeComponent.tsx src/Explorer/Controls/TreeComponent/TreeComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/EditorNeighborsComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/EditorNodePropertiesComponent.test.tsx
src/Explorer/Graph/GraphExplorerComponent/EditorNodePropertiesComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphExplorerAdapter.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/LeftPaneComponent.tsx src/Explorer/Graph/GraphExplorerComponent/LeftPaneComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.test.tsx src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.test.tsx
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.tsx src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/QueryContainerComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNeighborsComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
src/Explorer/Menus/CommandBar/CommandBarUtil.tsx src/Explorer/Menus/CommandBar/CommandBarUtil.tsx
src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.test.tsx
src/Explorer/Notebook/NotebookComponent/NotebookComponent.tsx
src/Explorer/Notebook/NotebookComponent/NotebookComponentAdapter.tsx src/Explorer/Notebook/NotebookComponent/NotebookComponentAdapter.tsx
src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx
src/Explorer/Notebook/NotebookComponent/VirtualCommandBarComponent.tsx src/Explorer/Notebook/NotebookComponent/VirtualCommandBarComponent.tsx
src/Explorer/Notebook/NotebookComponent/contents/file/index.tsx
src/Explorer/Notebook/NotebookComponent/contents/file/text-file.tsx
src/Explorer/Notebook/NotebookComponent/contents/index.tsx src/Explorer/Notebook/NotebookComponent/contents/index.tsx
src/Explorer/Notebook/NotebookRenderer/AzureTheme.tsx
src/Explorer/Notebook/NotebookRenderer/NotebookReadOnlyRenderer.tsx src/Explorer/Notebook/NotebookRenderer/NotebookReadOnlyRenderer.tsx
src/Explorer/Notebook/NotebookRenderer/NotebookRenderer.tsx src/Explorer/Notebook/NotebookRenderer/NotebookRenderer.tsx
src/Explorer/Notebook/NotebookRenderer/Prompt.tsx
src/Explorer/Notebook/NotebookRenderer/PromptContent.tsx
src/Explorer/Notebook/NotebookRenderer/StatusBar.test.tsx
src/Explorer/Notebook/NotebookRenderer/StatusBar.tsx
src/Explorer/Notebook/NotebookRenderer/Toolbar.tsx
src/Explorer/Notebook/NotebookRenderer/decorators/CellCreator.tsx
src/Explorer/Notebook/NotebookRenderer/decorators/CellLabeler.tsx
src/Explorer/Notebook/NotebookRenderer/decorators/HoverableCell.tsx
src/Explorer/Notebook/NotebookRenderer/decorators/draggable/index.tsx src/Explorer/Notebook/NotebookRenderer/decorators/draggable/index.tsx
src/Explorer/Notebook/NotebookRenderer/decorators/hijack-scroll/index.tsx src/Explorer/Notebook/NotebookRenderer/decorators/hijack-scroll/index.tsx
src/Explorer/Notebook/NotebookRenderer/decorators/kbd-shortcuts/index.tsx src/Explorer/Notebook/NotebookRenderer/decorators/kbd-shortcuts/index.tsx
src/Explorer/Notebook/temp/inputs/connected-editors/codemirror.tsx src/Explorer/Notebook/temp/inputs/connected-editors/codemirror.tsx
src/Explorer/Notebook/temp/inputs/editor.tsx
src/Explorer/Notebook/temp/markdown-cell.tsx
src/Explorer/Notebook/temp/source.tsx
src/Explorer/Notebook/temp/syntax-highlighter/index.tsx
src/Explorer/SplashScreen/SplashScreen.tsx
src/Explorer/Tabs/GalleryTab.tsx
src/Explorer/Tabs/NotebookViewerTab.tsx
src/Explorer/Tabs/TerminalTab.tsx
src/Explorer/Tree/ResourceTreeAdapter.tsx src/Explorer/Tree/ResourceTreeAdapter.tsx
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.tsx
src/GalleryViewer/Cards/GalleryCardComponent.tsx
src/GalleryViewer/GalleryViewer.tsx
src/GalleryViewer/GalleryViewerComponent.tsx
__mocks__/monaco-editor.ts __mocks__/monaco-editor.ts
src/Explorer/Tree/ResourceTree.tsx src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx

View File

@@ -92,11 +92,11 @@ jobs:
name: dist name: dist
path: dist/ path: dist/
- name: Upload build to preview blob storage - name: Upload build to preview blob storage
run: az storage blob upload-batch -d '$web' -s 'dist' --account-name cosmosexplorerpreview --subscription cosmosdb-portalteam-generaldemo --destination-path "${{github.event.pull_request.head.sha || github.sha}}" --account-key="${PREVIEW_STORAGE_KEY}" run: az storage blob upload-batch -d '$web' -s 'dist' --account-name cosmosexplorerpreview --subscription cosmosdb-portalteam-generaldemo --destination-path "${{github.event.pull_request.head.sha}}" --account-key="${PREVIEW_STORAGE_KEY}"
env: env:
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }} PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
- name: Upload preview config to blob storage - name: Upload preview config to blob storage
run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --subscription cosmosdb-portalteam-generaldemo --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}" run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --subscription cosmosdb-portalteam-generaldemo --name "${{github.event.pull_request.head.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}"
env: env:
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }} PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
endtoendemulator: endtoendemulator:

View File

@@ -7,7 +7,7 @@ on:
workflow_dispatch: workflow_dispatch:
schedule: schedule:
# Once every hour # Once every hour
- cron: "0 15 * * *" - cron: "0 * * * *"
# A workflow run is made up of one or more jobs that can run sequentially or in parallel # A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs: jobs:

View File

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

View File

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

View File

@@ -200,12 +200,4 @@
.migration:disabled { .migration:disabled {
background-color: #ccc; background-color: #ccc;
}
.trigger-field {
width: 40%;
margin-top: 10px
}
.trigger-form {
padding: 10px 30px 10px 30px;
} }

View File

@@ -2,7 +2,6 @@
.dataResourceTree { .dataResourceTree {
margin-left: @MediumSpace; margin-left: @MediumSpace;
overflow: auto;
.databaseHeader { .databaseHeader {
font-size: 14px; font-size: 14px;

View File

@@ -1,270 +1,272 @@
@import "./Common/Constants"; @import "./Common/Constants";
.resourceTree { .resourceTree {
height: 100%;
flex: 0 0 auto;
.main {
height: 100%; height: 100%;
} flex: 0 0 auto;
.main {
height: 100%;
}
} }
.resourceTreeScroll { .resourceTreeScroll {
height: 100%; height: 100%;
display: flex; display: flex;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
padding-right: 10px; padding-right: 10px;
} }
.userSelectNone { .userSelectNone {
-webkit-user-select: none; -webkit-user-select: none;
-moz-user-select: none; -moz-user-select: none;
-ms-user-select: none; -ms-user-select: none;
} }
.treeHovermargin { .treeHovermargin {
margin-left: 16px; margin-left: 16px;
} }
.highlight { .highlight {
padding: @SmallSpace 2px; padding: @SmallSpace 2px;
outline: 0; outline: 0;
&:hover { &:hover {
.hover(); .hover();
} }
&:active { &:active {
.active(); .active();
} }
&:focus { &:focus {
.focus(); .focus();
} }
} }
.contextmenushowing { .contextmenushowing {
background-color: #eee; background-color: #EEE;
} }
.collectionstree { .collectionstree {
width: 100%; width: 100%;
margin-top: @DefaultSpace; margin-top: @DefaultSpace;
.databaseList {
list-style-type: none;
padding-left: 0px;
.collectionList { .databaseList {
padding-left: (2 * @MediumSpace); 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 { .pointerCursor {
cursor: pointer; cursor: pointer;
} }
.menuEllipsis { .menuEllipsis {
padding-right: 6px; padding-right: 6px;
font-weight: bold; font-weight: bold;
font-size: 18px; font-size: 18px;
position: relative; position: relative;
top: -5px; top: -5px;
left: 0px; left: 0px;
float: right; float: right;
display: none; display: none;
padding-left: 6px !important; padding-left: 6px!important;
line-height: @TreeLineHeight; line-height: @TreeLineHeight;
} }
.databaseMenu { .databaseMenu {
.flex-display(); .flex-display();
} }
.databaseMenu:hover .menuEllipsis, .databaseMenu:hover .menuEllipsis,
.databaseMenu:focus .menuEllipsis { .databaseMenu:focus .menuEllipsis {
display: block; display: block;
} }
.databaseCollChildTextOverflow { .databaseCollChildTextOverflow {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
flex: 1; flex: 1;
} }
.collectionMenu { .collectionMenu {
.flex-display(); .flex-display();
} }
.collectionMenu:hover .menuEllipsis, .collectionMenu:hover .menuEllipsis,
.collectionMenu:focus .menuEllipsis { .collectionMenu:focus .menuEllipsis {
display: block; display: block;
} }
.documentsMenu:hover .menuEllipsis, .documentsMenu:hover .menuEllipsis,
.documentsMenu:focus .menuEllipsis { .documentsMenu:focus .menuEllipsis {
display: block; display: block;
} }
.treeChildMenu { .treeChildMenu {
display: flex; display: flex;
} }
.storedProcedureMenu:hover .menuEllipsis, .storedProcedureMenu:hover .menuEllipsis,
.storedProcedureMenu:focus .menuEllipsis { .storedProcedureMenu:focus .menuEllipsis {
display: block; display: block;
} }
.childMenu { .childMenu {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
padding-left: (6 * @MediumSpace); padding-left: (6 * @MediumSpace);
width: 100%; width: 100%;
} }
.storedChildMenu:hover .menuEllipsis, .storedChildMenu:hover .menuEllipsis,
.storedChildMenu:focus .menuEllipsis { .storedChildMenu:focus .menuEllipsis {
display: block; display: block;
} }
.contextmenu6 { .contextmenu6 {
top: -29px; top: -29px;
} }
.userDefinedMenu:hover .contextmenu6 { .userDefinedMenu:hover .contextmenu6 {
display: block; display: block;
} }
.userDefinedchildMenu:hover .menuEllipsis, .userDefinedchildMenu:hover .menuEllipsis,
.userDefinedchildMenu:focus .menuEllipsis { .userDefinedchildMenu:focus .menuEllipsis {
display: block; display: block;
} }
.triggersMenu:hover .menuEllipsis, .triggersMenu:hover .menuEllipsis,
.triggersMenu:focus .menuEllipsis { .triggersMenu:focus .menuEllipsis {
display: block; display: block;
} }
.triggersChildMenu:hover .menuEllipsis, .triggersChildMenu:hover .menuEllipsis,
.triggersChildMenu:focus .menuEllipsis { .triggersChildMenu:focus .menuEllipsis {
display: block; display: block;
} }
.databaseId { .databaseId {
font-size: 14px; font-size: 14px;
} }
.storedUdfTriggerMenu { .storedUdfTriggerMenu {
padding-left: 0px; padding-left: 0px;
} }
.collectionstree img { .collectionstree img {
width: 16px; width: 16px;
height: 16px; height: 16px;
vertical-align: text-top; vertical-align: text-top;
} }
img.collectionsTreeCollapseExpand { img.collectionsTreeCollapseExpand {
width: 10px; width: 10px;
height: 10px; height: 10px;
vertical-align: middle; vertical-align: middle;
margin-bottom: 5px; margin-bottom: 5px;
} }
.collapsed::before { .collapsed::before {
content: "\23F5"; content: "\23F5";
margin-left: 0px; margin-left: 0px;
font-size: 15px; font-size: 15px;
} }
.expanded::before { .expanded::before {
content: "\23F7"; content: '\23F7';
margin-left: 0px; margin-left: 0px;
font-size: 15px; font-size: 15px;
} }
.collectionMenuChildren { .collectionMenuChildren {
padding-left: 42px; padding-left: 42px;
} }
.main-nav { .main-nav {
width: 100vh; width: 100vh;
height: 40px; height: 40px;
background: white; background: white;
transform-origin: left top; transform-origin: left top;
-webkit-transform-origin: left top; -webkit-transform-origin: left top;
-ms-transform-origin: left top; -ms-transform-origin: left top;
transform: rotate(-90deg) translateX(-100%); transform: rotate(-90deg) translateX(-100%);
-webkit-transform: rotate(-90deg) translateX(-100%); -webkit-transform: rotate(-90deg) translateX(-100%);
-ms-transform: rotate(-90deg) translateX(-100%); -ms-transform: rotate(-90deg) translateX(-100%);
border-bottom: 1px solid #ccc; border-bottom: 1px solid #CCC;
} }
.main-nav-img { .main-nav-img {
width: 16px; width: 16px;
height: 16px; height: 16px;
margin: -32px 0 0 0; margin: -32px 0 0 0;
transform: rotate(-90deg) translateX(-100%); transform: rotate(-90deg) translateX(-100%);
-webkit-transform: rotate(-90deg) translateX(-100%); -webkit-transform: rotate(-90deg) translateX(-100%);
-ms-transform: rotate(-90deg) translateX(-100%); -ms-transform: rotate(-90deg) translateX(-100%);
} }
.main-nav-img.main-nav-sub-img { .main-nav-img.main-nav-sub-img {
width: 16px; width: 16px;
height: 16px; height: 16px;
margin: 0px 0px 0 0; margin: 0px 0px 0 0;
transform: rotate(180deg) translateX(0%); transform: rotate(180deg) translateX(0%);
-webkit-transform: rotate(180deg) translateX(0%); -webkit-transform: rotate(180deg) translateX(0%);
-ms-transform: rotate(180deg) translateX(0%); -ms-transform: rotate(180deg) translateX(0%);
position: absolute; position: absolute;
right: -8px; right: -8px;
top: 16px; top: 16px;
} }
ul.nav { ul.nav {
margin: 0 auto; margin: 0 auto;
margin-top: 0px; margin-top: 0px;
margin-left: 0px; margin-left: 0px;
} }
.mini ul.nav li { .mini ul.nav li {
float: right; float: right;
line-height: 25px; line-height: 25px;
height: auto; height: auto;
margin-top: 3px; margin-top: 3px;
} }
.spancolchildstyle { .spancolchildstyle {
padding: 4px; padding: 4px;
} }
.contextmenubutton { .contextmenubutton {
float: right; float: right;
display: none; display: none;
} }
.highlight:hover > .contextmenubutton { .highlight:hover>.contextmenubutton {
display: unset; display: unset;
} }
.highlight:hover > .contextmenubutton::after { .highlight:hover>.contextmenubutton::after {
content: "\2026"; content: "\2026";
font-size: 12px; font-size: 12px;
} }
.showEllipsis { .showEllipsis {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
} }

177
package-lock.json generated
View File

@@ -3709,84 +3709,14 @@
} }
}, },
"@nteract/editor": { "@nteract/editor": {
"version": "10.1.12", "version": "10.1.2",
"resolved": "https://registry.npmjs.org/@nteract/editor/-/editor-10.1.12.tgz", "resolved": "https://registry.npmjs.org/@nteract/editor/-/editor-10.1.2.tgz",
"integrity": "sha512-bsUrCctukjWdpKNWQOQmhfxMCQ/SBVIO6+RkazI4y4dVeeP3KMP8nxfhzIbzTMNSkyynps/deZFjpDWqRhG+Dg==", "integrity": "sha512-Wtj0kJUSoBZsWUh82JGt6miqYS0jt0k+3SD3cnW9socayxp2KB0Qbqhh2NtrF9ysxVHWnQT8iUarJjpGIdNyng==",
"requires": { "requires": {
"@nteract/messaging": "^7.0.19", "@nteract/messaging": "^7.0.10",
"@nteract/outputs": "^3.0.11", "@nteract/outputs": "^3.0.9",
"codemirror": "5.61.1", "codemirror": "5.57.0",
"rxjs": "^6.3.3" "rxjs": "^6.3.3"
},
"dependencies": {
"@nteract/commutable": {
"version": "7.4.5",
"resolved": "https://registry.npmjs.org/@nteract/commutable/-/commutable-7.4.5.tgz",
"integrity": "sha512-RYqyMvkFt/04GQ9T+hGYgr9/LEy0dAYJ2QKn930TFX004KjfBT6Tt8VSLFyHWkXqPwyJ0jKMCJwqLcGOI/atqg==",
"requires": {
"immutable": "^4.0.0-rc.12",
"uuid": "^8.0.0"
}
},
"@nteract/messaging": {
"version": "7.0.19",
"resolved": "https://registry.npmjs.org/@nteract/messaging/-/messaging-7.0.19.tgz",
"integrity": "sha512-gRPMxJr741/BshrfCcPSbm5iVyRU2TKmAv9jeQzk0MZEGy+Y1A0REO+eptkt4Ma0OXlvDxON6JEDauk8+2xt4w==",
"requires": {
"@nteract/types": "^7.1.9",
"@types/uuid": "^8.0.0",
"lodash.clonedeep": "^4.5.0",
"rxjs": "^6.6.0",
"uuid": "^8.0.0"
}
},
"@nteract/outputs": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/@nteract/outputs/-/outputs-3.0.11.tgz",
"integrity": "sha512-LeT9ViBf+fTPSubZ9dMe7128kg0rl1jIG54V0n2GiU5RuYnUz21FU0IOaLMPUfFMO1VyVEOW5jDc3PAQx5/Kwg==",
"requires": {
"@nteract/markdown": "^4.5.2",
"@nteract/mathjax": "^4.0.11",
"ansi-to-react": "^6.0.5",
"react-json-tree": "^0.12.1"
}
},
"@nteract/types": {
"version": "7.1.9",
"resolved": "https://registry.npmjs.org/@nteract/types/-/types-7.1.9.tgz",
"integrity": "sha512-a7lGMWdjfz2QGlZbAiFHifU9Nhk9ntwg/iKUTMIMRPY1Wfs5UreHSMt+vZ8OY5HGjxicfHozBatGDKXeKXFHMQ==",
"requires": {
"@nteract/commutable": "^7.4.5",
"immutable": "^4.0.0-rc.12",
"rxjs": "^6.6.0",
"uuid": "^8.0.0"
}
},
"react-base16-styling": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.7.0.tgz",
"integrity": "sha512-lTa/VSFdU6BOAj+FryOe7OTZ0OBP8GXPOnCS0QnZi7G3zhssWgIgwl0eUL77onXx/WqKPFndB3ZeC77QC/l4Dw==",
"requires": {
"base16": "^1.0.0",
"lodash.curry": "^4.1.1",
"lodash.flow": "^3.5.0",
"pure-color": "^1.3.0"
}
},
"react-json-tree": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.12.1.tgz",
"integrity": "sha512-j6fkRY7ha9XMv1HPVakRCsvyFwHGR5AZuwO8naBBeZXnZbbLor5tpcUxS/8XD01+D1v7ZN5p+7LU+9V1uyASiQ==",
"requires": {
"prop-types": "^15.7.2",
"react-base16-styling": "^0.7.0"
}
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
}
} }
}, },
"@nteract/epics": { "@nteract/epics": {
@@ -5583,10 +5513,11 @@
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
"integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==" "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA=="
}, },
"@types/lodash": { "@types/memoize-one": {
"version": "4.14.171", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.171.tgz", "resolved": "https://registry.npmjs.org/@types/memoize-one/-/memoize-one-4.1.1.tgz",
"integrity": "sha512-7eQ2xYLLI/LsicL2nejW9Wyko3lcpN6O/z0ZLHrEQsg280zIdCv1t/0m6UtBjUHokCGBQ3gYTbHzDkZ1xOBwwg==" "integrity": "sha512-+9djKUUn8hOyktLCfCy4hLaIPgDNovaU36fsnZe9trFHr6ddlbIn2q0SEsnkCkNR+pBWEU440Molz/+Mpyf+gQ==",
"dev": true
}, },
"@types/minimatch": { "@types/minimatch": {
"version": "3.0.3", "version": "3.0.3",
@@ -5658,6 +5589,12 @@
"integrity": "sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==", "integrity": "sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==",
"dev": true "dev": true
}, },
"@types/promise.prototype.finally": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/promise.prototype.finally/-/promise.prototype.finally-2.0.3.tgz",
"integrity": "sha512-hQfmCK9Hw8diRIa3KoIDY4aimdxckamHUcmaZeB9tBMyb/Shi1yCBIPfry+nqN4jILNVThY1tnTwdMhQeMjqrw==",
"dev": true
},
"@types/prop-types": { "@types/prop-types": {
"version": "15.5.8", "version": "15.5.8",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.8.tgz", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.8.tgz",
@@ -5725,15 +5662,6 @@
"redux": "^4.0.0" "redux": "^4.0.0"
} }
}, },
"@types/react-splitter-layout": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/react-splitter-layout/-/react-splitter-layout-3.0.1.tgz",
"integrity": "sha512-NsKq32LdG11G/Uj+xo2QmC9S8YSe8JRtxkBhsBE7ODFs0zcnzNEqFAQirP0H7rPe2WFGiu+d/44xbHsew7QAJw==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/react-table": { "@types/react-table": {
"version": "6.8.7", "version": "6.8.7",
"resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-6.8.7.tgz", "resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-6.8.7.tgz",
@@ -8142,9 +8070,9 @@
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
}, },
"codemirror": { "codemirror": {
"version": "5.61.1", "version": "5.57.0",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.61.1.tgz", "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.57.0.tgz",
"integrity": "sha512-+D1NZjAucuzE93vJGbAaXzvoBHwp9nJZWWWF9utjv25+5AZUiah6CIlfb4ikG4MoDsFsCG8niiJH5++OO2LgIQ==" "integrity": "sha512-WGc6UL7Hqt+8a6ZAsj/f1ApQl3NPvHY/UQSzG6fB6l4BjExgVdhFaxd7mRTw1UCiYe/6q86zHP+kfvBQcZGvUg=="
}, },
"collapse-white-space": { "collapse-white-space": {
"version": "1.0.6", "version": "1.0.6",
@@ -10818,6 +10746,12 @@
"integrity": "sha512-uoeyx2D5LawJdziMdweOp6cnZzFOOPT9VvPG6gOh6YC7N9pU0k2KpVlRiz/Vc/fFBiGUNNeJq2Aq+9GJ65Nfrw==", "integrity": "sha512-uoeyx2D5LawJdziMdweOp6cnZzFOOPT9VvPG6gOh6YC7N9pU0k2KpVlRiz/Vc/fFBiGUNNeJq2Aq+9GJ65Nfrw==",
"dev": true "dev": true
}, },
"expose-loader": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-0.7.5.tgz",
"integrity": "sha512-iPowgKUZkTPX5PznYsmifVj9Bob0w2wTHVkt/eYNPSzyebkUgIedmskf/kcfEIWpiWjg3JRjnW+a17XypySMuw==",
"dev": true
},
"express": { "express": {
"version": "4.17.1", "version": "4.17.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
@@ -17774,6 +17708,12 @@
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true "dev": true
}, },
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"supports-color": { "supports-color": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -18577,9 +18517,9 @@
} }
}, },
"lodash": { "lodash": {
"version": "4.17.21", "version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
}, },
"lodash-es": { "lodash-es": {
"version": "4.17.20", "version": "4.17.20",
@@ -18806,9 +18746,9 @@
"integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==" "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg=="
}, },
"marked": { "marked": {
"version": "2.0.6", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/marked/-/marked-2.0.6.tgz", "resolved": "https://registry.npmjs.org/marked/-/marked-2.0.3.tgz",
"integrity": "sha512-S2mYj0FzTQa0dLddssqwRVW4EOJOVJ355Xm2Vcbm+LU7GQRGWvwbO5K87OaPSOux2AwTSgtPPaXmc8sDPrhn2A==", "integrity": "sha512-5otztIIcJfPc2qGTN8cVtOJEjNJZ0jwa46INMagrYfk0EvqtRuEHLsEe0LrFS0/q+ZRKT0+kXK7P2T1AN5lWRA==",
"dev": true "dev": true
}, },
"martinez-polygon-clipping": { "martinez-polygon-clipping": {
@@ -20623,9 +20563,9 @@
} }
}, },
"playwright": { "playwright": {
"version": "1.13.0", "version": "1.10.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.13.0.tgz", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.10.0.tgz",
"integrity": "sha512-GA5OyEeKx1v/pRcANmYncCT67Y7Y4N5zLRU5E690dn/Id10sooR5hQZmCDYsjXlutZb/1q0R3sITALnvhEjCjg==", "integrity": "sha512-b7SGBcCPq4W3pb4ImEDmNXtO0ZkJbZMuWiShsaNJd+rGfY/6fqwgllsAojmxGSgFmijYw7WxCoPiAIEDIH16Kw==",
"dev": true, "dev": true,
"requires": { "requires": {
"commander": "^6.1.0", "commander": "^6.1.0",
@@ -20640,8 +20580,7 @@
"proxy-from-env": "^1.1.0", "proxy-from-env": "^1.1.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"stack-utils": "^2.0.3", "stack-utils": "^2.0.3",
"ws": "^7.4.6", "ws": "^7.3.1"
"yazl": "^2.5.1"
}, },
"dependencies": { "dependencies": {
"commander": { "commander": {
@@ -20673,12 +20612,6 @@
"requires": { "requires": {
"escape-string-regexp": "^2.0.0" "escape-string-regexp": "^2.0.0"
} }
},
"ws": {
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz",
"integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==",
"dev": true
} }
} }
}, },
@@ -21720,11 +21653,6 @@
"react-is": "^16.9.0" "react-is": "^16.9.0"
} }
}, },
"react-splitter-layout": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/react-splitter-layout/-/react-splitter-layout-4.0.0.tgz",
"integrity": "sha512-SLqOjBOxRuizWUa83w6q5/u9cDWa9/yj9Iko9V9JFN8x+cqIXiDlUFWSx+icz3IIgvsN/oRIw3za5/32RjIwrA=="
},
"react-syntax-highlighter": { "react-syntax-highlighter": {
"version": "12.2.1", "version": "12.2.1",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-12.2.1.tgz", "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-12.2.1.tgz",
@@ -24457,6 +24385,12 @@
"universalify": "^2.0.0" "universalify": "^2.0.0"
} }
}, },
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"universalify": { "universalify": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
@@ -24472,9 +24406,9 @@
"dev": true "dev": true
}, },
"typescript": { "typescript": {
"version": "4.3.4", "version": "4.2.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
"integrity": "sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew==", "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==",
"dev": true "dev": true
}, },
"typestyle": { "typestyle": {
@@ -26169,15 +26103,6 @@
"fd-slicer": "~1.1.0" "fd-slicer": "~1.1.0"
} }
}, },
"yazl": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz",
"integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==",
"dev": true,
"requires": {
"buffer-crc32": "~0.2.3"
}
},
"yocto-queue": { "yocto-queue": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View File

@@ -22,7 +22,7 @@
"@nteract/data-explorer": "8.0.3", "@nteract/data-explorer": "8.0.3",
"@nteract/directory-listing": "2.0.6", "@nteract/directory-listing": "2.0.6",
"@nteract/dropdown-menu": "1.0.1", "@nteract/dropdown-menu": "1.0.1",
"@nteract/editor": "10.1.12", "@nteract/editor": "10.1.2",
"@nteract/fixtures": "2.3.0", "@nteract/fixtures": "2.3.0",
"@nteract/iron-icons": "1.0.0", "@nteract/iron-icons": "1.0.0",
"@nteract/jupyter-widgets": "2.0.0", "@nteract/jupyter-widgets": "2.0.0",
@@ -42,7 +42,6 @@
"@octokit/rest": "17.9.2", "@octokit/rest": "17.9.2",
"@phosphor/widgets": "1.9.3", "@phosphor/widgets": "1.9.3",
"@testing-library/jest-dom": "5.11.9", "@testing-library/jest-dom": "5.11.9",
"@types/lodash": "4.14.171",
"@types/mkdirp": "1.0.1", "@types/mkdirp": "1.0.1",
"@types/node-fetch": "2.5.7", "@types/node-fetch": "2.5.7",
"applicationinsights": "1.8.0", "applicationinsights": "1.8.0",
@@ -90,7 +89,6 @@
"react-i18next": "11.8.5", "react-i18next": "11.8.5",
"react-notification-system": "0.2.17", "react-notification-system": "0.2.17",
"react-redux": "7.1.3", "react-redux": "7.1.3",
"react-splitter-layout": "4.0.0",
"redux": "4.0.4", "redux": "4.0.4",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",
"rx-jupyter": "5.5.12", "rx-jupyter": "5.5.12",
@@ -118,14 +116,15 @@
"@types/enzyme-adapter-react-16": "1.0.6", "@types/enzyme-adapter-react-16": "1.0.6",
"@types/hasher": "0.0.31", "@types/hasher": "0.0.31",
"@types/jest": "26.0.20", "@types/jest": "26.0.20",
"@types/memoize-one": "4.1.1",
"@types/node": "12.11.1", "@types/node": "12.11.1",
"@types/post-robot": "10.0.1", "@types/post-robot": "10.0.1",
"@types/promise.prototype.finally": "2.0.3",
"@types/q": "1.5.1", "@types/q": "1.5.1",
"@types/react": "17.0.3", "@types/react": "17.0.3",
"@types/react-dom": "17.0.3", "@types/react-dom": "17.0.3",
"@types/react-notification-system": "0.2.39", "@types/react-notification-system": "0.2.39",
"@types/react-redux": "7.1.7", "@types/react-redux": "7.1.7",
"@types/react-splitter-layout": "3.0.1",
"@types/sanitize-html": "1.27.2", "@types/sanitize-html": "1.27.2",
"@types/sinon": "2.3.3", "@types/sinon": "2.3.3",
"@types/styled-components": "5.1.1", "@types/styled-components": "5.1.1",
@@ -147,6 +146,7 @@
"eslint-plugin-prefer-arrow": "1.2.2", "eslint-plugin-prefer-arrow": "1.2.2",
"eslint-plugin-react-hooks": "4.2.0", "eslint-plugin-react-hooks": "4.2.0",
"expect-playwright": "0.3.3", "expect-playwright": "0.3.3",
"expose-loader": "0.7.5",
"fast-glob": "3.2.5", "fast-glob": "3.2.5",
"file-loader": "2.0.0", "file-loader": "2.0.0",
"fs-extra": "7.0.0", "fs-extra": "7.0.0",
@@ -164,7 +164,7 @@
"mini-css-extract-plugin": "0.4.3", "mini-css-extract-plugin": "0.4.3",
"monaco-editor-webpack-plugin": "1.7.0", "monaco-editor-webpack-plugin": "1.7.0",
"node-fetch": "2.6.1", "node-fetch": "2.6.1",
"playwright": "1.13.0", "playwright": "1.10.0",
"prettier": "2.2.1", "prettier": "2.2.1",
"raw-loader": "0.5.1", "raw-loader": "0.5.1",
"react-dev-utils": "11.0.4", "react-dev-utils": "11.0.4",
@@ -175,7 +175,7 @@
"tslint": "5.11.0", "tslint": "5.11.0",
"tslint-microsoft-contrib": "6.0.0", "tslint-microsoft-contrib": "6.0.0",
"typedoc": "0.20.36", "typedoc": "0.20.36",
"typescript": "4.3.4", "typescript": "4.2.4",
"url-loader": "1.1.1", "url-loader": "1.1.1",
"wait-on": "4.0.2", "wait-on": "4.0.2",
"webpack": "4.46.0", "webpack": "4.46.0",
@@ -208,7 +208,7 @@
"strict:find": "node ./strict-null-checks/find.js", "strict:find": "node ./strict-null-checks/find.js",
"strict:add": "node ./strict-null-checks/auto-add.js", "strict:add": "node ./strict-null-checks/auto-add.js",
"compile:fullStrict": "tsc -p ./tsconfig.json --strictNullChecks", "compile:fullStrict": "tsc -p ./tsconfig.json --strictNullChecks",
"generateARMClients": "npx ts-node --compiler-options '{\"module\":\"commonjs\"}' utils/armClientGenerator/generator.ts" "generateARMClients": "ts-node --compiler-options '{\"module\":\"commonjs\"}' utils/armClientGenerator/generator.ts"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -1,4 +1,3 @@
{ {
"PROXY_PATH": "/proxy", "PROXY_PATH": "/proxy"
"msalRedirectURI": "https://cosmos-explorer-preview.azurewebsites.net/"
} }

View File

@@ -62,17 +62,6 @@ app.get("/pull/:pr(\\d+)", (req, res) => {
}) })
.catch(() => res.sendStatus(500)); .catch(() => res.sendStatus(500));
}); });
app.get("/", (req, res) => {
fetch("https://api.github.com/repos/Azure/cosmos-explorer/branches/master")
.then((response) => response.json())
.then(({ commit: { sha } }) => {
const explorer = new URL(
"https://cosmos-explorer-preview.azurewebsites.net/commit/" + sha + "/hostedExplorer.html"
);
return res.redirect(explorer.href);
})
.catch(() => res.sendStatus(500));
});
app.listen(port, () => { app.listen(port, () => {
console.log(`Example app listening on port: ${port}`); console.log(`Example app listening on port: ${port}`);

View File

@@ -1,36 +0,0 @@
import React, { FunctionComponent } from "react";
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
import { userContext } from "../UserContext";
export interface CollapsedResourceTreeProps {
toggleLeftPaneExpanded: () => void;
isLeftPaneExpanded: boolean;
}
export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps> = ({
toggleLeftPaneExpanded,
isLeftPaneExpanded,
}: CollapsedResourceTreeProps): JSX.Element => {
return (
<div id="mini" className={!isLeftPaneExpanded ? "mini toggle-mini" : "hiddenMain"}>
<div className="main-nav nav">
<ul className="nav">
<li
className="resourceTreeCollapse"
id="collapseToggleLeftPaneButton"
role="button"
tabIndex={0}
aria-label="Expand Tree"
>
<span className="leftarrowCollapsed" onClick={toggleLeftPaneExpanded}>
<img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" />
</span>
<span className="collectionCollapsed" onClick={toggleLeftPaneExpanded}>
<span>{userContext.apiType} API</span>
</span>
</li>
</ul>
</div>
</div>
);
};

View File

@@ -44,7 +44,7 @@ export class ArmResourceTypes {
} }
export class BackendDefaults { export class BackendDefaults {
public static partitionKeyKind = "Hash"; public static partitionKeyKind: string = "Hash";
public static singlePartitionStorageInGb: string = "10"; public static singlePartitionStorageInGb: string = "10";
public static multiPartitionStorageInGb: string = "100"; public static multiPartitionStorageInGb: string = "100";
public static maxChangeFeedRetentionDuration: number = 10; public static maxChangeFeedRetentionDuration: number = 10;
@@ -94,7 +94,7 @@ export class Flights {
public static readonly MongoIndexEditor = "mongoindexeditor"; public static readonly MongoIndexEditor = "mongoindexeditor";
public static readonly MongoIndexing = "mongoindexing"; public static readonly MongoIndexing = "mongoindexing";
public static readonly AutoscaleTest = "autoscaletest"; public static readonly AutoscaleTest = "autoscaletest";
public static readonly PartitionKeyTest = "partitionkeytest"; public static readonly SchemaAnalyzer = "schemaanalyzer";
} }
export class AfecFeatures { export class AfecFeatures {
@@ -158,6 +158,16 @@ export class DocumentsGridMetrics {
public static DocumentEditorMaxWidthRatio: number = 0.4; public static DocumentEditorMaxWidthRatio: number = 0.4;
} }
export class ExplorerMetrics {
public static SplitterMinWidth: number = 240;
public static SplitterMaxWidth: number = 400;
public static CollapsedResourceTreeWidth: number = 36;
}
export class SplitterMetrics {
public static CollapsedPositionLeft: number = ExplorerMetrics.CollapsedResourceTreeWidth;
}
export class Areas { export class Areas {
public static ResourceTree: string = "Resource Tree"; public static ResourceTree: string = "Resource Tree";
public static ContextualPane: string = "Contextual Pane"; public static ContextualPane: string = "Contextual Pane";

View File

@@ -83,7 +83,7 @@ export function client(): Cosmos.CosmosClient {
if (_client) return _client; if (_client) return _client;
const options: Cosmos.CosmosClientOptions = { const options: Cosmos.CosmosClientOptions = {
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
key: userContext.masterKey, ...(!userContext.features.enableAadDataPlane && { key: userContext.masterKey }),
tokenProvider, tokenProvider,
connectionPolicy: { connectionPolicy: {
enableEndpointDiscovery: false, enableEndpointDiscovery: false,

View File

@@ -1,17 +0,0 @@
import { userContext } from "../UserContext";
function isVirtualNetworkFilterEnabled() {
return userContext.databaseAccount?.properties?.isVirtualNetworkFilterEnabled;
}
function isIpRulesEnabled() {
return userContext.databaseAccount?.properties?.ipRules?.length > 0;
}
function isPrivateEndpointConnectionsEnabled() {
return userContext.databaseAccount?.properties?.privateEndpointConnections?.length > 0;
}
export function isPublicInternetAccessAllowed(): boolean {
return !isVirtualNetworkFilterEnabled() && !isIpRulesEnabled() && !isPrivateEndpointConnectionsEnabled();
}

View File

@@ -32,7 +32,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
<DatePicker <DatePicker
className="addEntityDatePicker" className="addEntityDatePicker"
placeholder={entityValuePlaceholder} placeholder={entityValuePlaceholder}
value={entityValue ? new Date(entityValue) : new Date()} value={entityValue && new Date(entityValue)}
ariaLabel={entityValuePlaceholder} ariaLabel={entityValuePlaceholder}
onSelectDate={onSelectDate} onSelectDate={onSelectDate}
disabled={isEntityValueDisable} disabled={isEntityValueDisable}
@@ -59,7 +59,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
disabled={isEntityValueDisable} disabled={isEntityValueDisable}
type={entityValueType} type={entityValueType}
placeholder={entityValuePlaceholder} placeholder={entityValuePlaceholder}
value={typeof entityValue === "string" ? entityValue : ""} value={typeof entityValue === "string" && entityValue}
onChange={onEntityValueChange} onChange={onEntityValueChange}
/> />
); );

View File

@@ -1,6 +1,6 @@
import * as ExplorerSettings from "../Shared/ExplorerSettings";
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
import * as HeadersUtility from "./HeadersUtility"; import * as HeadersUtility from "./HeadersUtility";
import { ExplorerSettings } from "../Shared/ExplorerSettings";
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
describe("Headers Utility", () => { describe("Headers Utility", () => {
describe("shouldEnableCrossPartitionKeyForResourceWithPartitionKey()", () => { describe("shouldEnableCrossPartitionKeyForResourceWithPartitionKey()", () => {

View File

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

View File

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

View File

@@ -1,23 +1,25 @@
import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import * as _ from "underscore"; import * as _ from "underscore";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import Explorer from "../Explorer/Explorer"; import Explorer from "../Explorer/Explorer";
import DocumentsTab from "../Explorer/Tabs/DocumentsTab"; import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
import DocumentId from "../Explorer/Tree/DocumentId"; import DocumentId from "../Explorer/Tree/DocumentId";
import { useDatabases } from "../Explorer/useDatabases";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import * as QueryUtils from "../Utils/QueryUtils";
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants"; import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
import { createCollection } from "./dataAccess/createCollection"; import { createCollection } from "./dataAccess/createCollection";
import { createDocument } from "./dataAccess/createDocument"; import { createDocument } from "./dataAccess/createDocument";
import { deleteDocument } from "./dataAccess/deleteDocument"; import { deleteDocument } from "./dataAccess/deleteDocument";
import { queryDocuments } from "./dataAccess/queryDocuments"; import { queryDocuments } from "./dataAccess/queryDocuments";
import { queryDocumentsPage } from "./dataAccess/queryDocumentsPage";
import { handleError } from "./ErrorHandlingUtils"; import { handleError } from "./ErrorHandlingUtils";
export class QueriesClient { export class QueriesClient {
private static readonly PartitionKey: DataModels.PartitionKey = { private static readonly PartitionKey: DataModels.PartitionKey = {
paths: [`/${SavedQueries.PartitionKeyProperty}`], paths: [`/${SavedQueries.PartitionKeyProperty}`],
kind: "Hash", kind: BackendDefaults.partitionKeyKind,
version: BackendDefaults.partitionKeyVersion, version: BackendDefaults.partitionKeyVersion,
}; };
private static readonly FetchQuery: string = "SELECT * FROM c"; private static readonly FetchQuery: string = "SELECT * FROM c";
@@ -98,35 +100,45 @@ export class QueriesClient {
const options: any = { enableCrossPartitionQuery: true }; const options: any = { enableCrossPartitionQuery: true };
const clearMessage = NotificationConsoleUtils.logConsoleProgress("Fetching saved queries"); const clearMessage = NotificationConsoleUtils.logConsoleProgress("Fetching saved queries");
const results = await queryDocuments( const queryIterator: QueryIterator<ItemDefinition & Resource> = queryDocuments(
SavedQueries.DatabaseName, SavedQueries.DatabaseName,
SavedQueries.CollectionName, SavedQueries.CollectionName,
this.fetchQueriesQuery(), this.fetchQueriesQuery(),
options options
).fetchAll(); );
const fetchQueries = async (firstItemIndex: number): Promise<ViewModels.QueryResults> =>
let queries: DataModels.Query[] = _.map(results.resources, (document: DataModels.Query) => { await queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex);
if (!document) { return QueryUtils.queryAllPages(fetchQueries)
return undefined; .then(
} (results: ViewModels.QueryResults) => {
const { id, resourceId, query, queryName } = document; let queries: DataModels.Query[] = _.map(results.documents, (document: DataModels.Query) => {
const parsedQuery: DataModels.Query = { if (!document) {
resourceId: resourceId, return undefined;
queryName: queryName, }
query: query, const { id, resourceId, query, queryName } = document;
id: id, const parsedQuery: DataModels.Query = {
}; resourceId: resourceId,
try { queryName: queryName,
this.validateQuery(parsedQuery); query: query,
return parsedQuery; id: id,
} catch (error) { };
return undefined; try {
} this.validateQuery(parsedQuery);
}); return parsedQuery;
queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery); } catch (error) {
NotificationConsoleUtils.logConsoleInfo("Successfully fetched saved queries"); return undefined;
clearMessage(); }
return queries; });
queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery);
NotificationConsoleUtils.logConsoleInfo("Successfully fetched saved queries");
return Promise.resolve(queries);
},
(error: any) => {
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
return Promise.reject(error);
}
)
.finally(() => clearMessage());
} }
public async deleteQuery(query: DataModels.Query): Promise<void> { public async deleteQuery(query: DataModels.Query): Promise<void> {
@@ -177,7 +189,7 @@ export class QueriesClient {
private findQueriesCollection(): ViewModels.Collection { private findQueriesCollection(): ViewModels.Collection {
const queriesDatabase: ViewModels.Database = _.find( const queriesDatabase: ViewModels.Database = _.find(
useDatabases.getState().databases, this.container.databases(),
(database: ViewModels.Database) => database.id() === SavedQueries.DatabaseName (database: ViewModels.Database) => database.id() === SavedQueries.DatabaseName
); );
if (!queriesDatabase) { if (!queriesDatabase) {

View File

@@ -1,66 +0,0 @@
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";
export interface ResourceTreeContainerProps {
toggleLeftPaneExpanded: () => void;
isLeftPaneExpanded: boolean;
container: Explorer;
}
export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps> = ({
toggleLeftPaneExpanded,
isLeftPaneExpanded,
container,
}: ResourceTreeContainerProps): JSX.Element => {
return (
<div id="main" className={isLeftPaneExpanded ? "main" : "hiddenMain"}>
{/* Collections Window - - Start */}
<div id="mainslide" className="flexContainer">
{/* Collections Window Title/Command Bar - Start */}
<div className="collectiontitle">
<div className="coltitle">
<span className="titlepadcol">{userContext.apiType} API</span>
<div className="float-right">
<span
className="padimgcolrefresh"
data-test="refreshTree"
role="button"
data-bind="click: onRefreshResourcesClick, clickBubble: false, event: { keypress: onRefreshDatabasesKeyPress }"
tabIndex={0}
aria-label="Refresh tree"
title="Refresh tree"
>
<img className="refreshcol" src={refreshImg} alt="Refresh Tree" />
</span>
<span
className="padimgcolrefresh1"
id="expandToggleLeftPaneButton"
role="button"
onClick={toggleLeftPaneExpanded}
tabIndex={0}
aria-label="Collapse Tree"
title="Collapse Tree"
>
<img className="refreshcol1" src={arrowLeftImg} alt="Hide" />
</span>
</div>
</div>
</div>
{userContext.authType === AuthType.ResourceToken ? (
<ResourceTokenTree />
) : userContext.features.enableKOResourceTree ? (
<div style={{ overflowY: "auto" }} data-bind="react:resourceTree" />
) : (
<ResourceTree container={container} />
)}
</div>
{/* Collections Window - End */}
</div>
);
};

View File

@@ -1,3 +1,7 @@
import * as ko from "knockout";
import { SplitterMetrics } from "./Constants";
export enum SplitterDirection { export enum SplitterDirection {
Horizontal = "horizontal", Horizontal = "horizontal",
Vertical = "vertical", Vertical = "vertical",
@@ -24,12 +28,14 @@ export class Splitter {
public lastX!: number; public lastX!: number;
public lastWidth!: number; public lastWidth!: number;
private isCollapsed: ko.Observable<boolean>;
private bounds: SplitterBounds; private bounds: SplitterBounds;
private direction: SplitterDirection; private direction: SplitterDirection;
constructor(options: SplitterOptions) { constructor(options: SplitterOptions) {
this.splitterId = options.splitterId; this.splitterId = options.splitterId;
this.leftSideId = options.leftId; this.leftSideId = options.leftId;
this.isCollapsed = ko.observable<boolean>(false);
this.bounds = options.bounds; this.bounds = options.bounds;
this.direction = options.direction; this.direction = options.direction;
this.initialize(); this.initialize();
@@ -77,4 +83,23 @@ export class Splitter {
}; };
private onResizeStop: JQueryUI.ResizableEvent = () => $("iframe").css("pointer-events", "auto"); private onResizeStop: JQueryUI.ResizableEvent = () => $("iframe").css("pointer-events", "auto");
public collapseLeft() {
this.lastX = $(this.splitter).position().left;
this.lastWidth = $(this.leftSide).width();
$(this.splitter).css("left", SplitterMetrics.CollapsedPositionLeft);
$(this.leftSide).css("width", "");
$(this.leftSide).resizable("option", "disabled", true).removeClass("ui-resizable-disabled"); // remove class so splitter is visible
$(this.splitter).removeClass("ui-resizable-e");
this.isCollapsed(true);
}
public expandLeft() {
$(this.splitter).addClass("ui-resizable-e");
$(this.leftSide).css("width", this.lastWidth);
$(this.splitter).css("left", this.lastX);
$(this.splitter).css("left", ""); // this ensures the splitter's position is not fixed and enables movement during resizing
$(this.leftSide).resizable("enable");
this.isCollapsed(false);
}
} }

View File

@@ -1,10 +1,7 @@
jest.mock("../../Utils/arm/request"); jest.mock("../../Utils/arm/request");
jest.mock("../CosmosClient"); jest.mock("../CosmosClient");
import ko from "knockout";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels"; import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels";
import { Database } from "../../Contracts/ViewModels";
import { useDatabases } from "../../Explorer/useDatabases";
import { updateUserContext } from "../../UserContext"; import { updateUserContext } from "../../UserContext";
import { armRequest } from "../../Utils/arm/request"; import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
@@ -26,15 +23,6 @@ describe("createCollection", () => {
} as DatabaseAccount, } as DatabaseAccount,
apiType: "SQL", apiType: "SQL",
}); });
useDatabases.setState({
databases: [
{
id: ko.observable("testDatabase"),
loadCollections: () => undefined,
collections: ko.observableArray([]),
} as Database,
],
});
}); });
it("should call ARM if logged in with AAD", async () => { it("should call ARM if logged in with AAD", async () => {

View File

@@ -4,17 +4,24 @@ import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/Contai
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest"; import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import { useDatabases } from "../../Explorer/useDatabases";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { getCollectionName } from "../../Utils/APITypeUtils"; import {
import { createUpdateCassandraTable } from "../../Utils/arm/generatedClients/cosmos/cassandraResources"; createUpdateCassandraTable,
import { createUpdateGremlinGraph } from "../../Utils/arm/generatedClients/cosmos/gremlinResources"; getCassandraTable,
import { createUpdateMongoDBCollection } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources"; } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { createUpdateSqlContainer } from "../../Utils/arm/generatedClients/cosmos/sqlResources"; import {
import { createUpdateTable } from "../../Utils/arm/generatedClients/cosmos/tableResources"; createUpdateGremlinGraph,
import * as ARMTypes from "../../Utils/arm/generatedClients/cosmos/types"; getGremlinGraph,
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import {
createUpdateMongoDBCollection,
getMongoDBCollection,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import * as ARMTypes from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
@@ -55,16 +62,6 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
}; };
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => { const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
if (!params.createNewDatabase) {
const isValid = await useDatabases.getState().validateCollectionId(params.databaseId, params.collectionId);
if (!isValid) {
const collectionName = getCollectionName().toLocaleLowerCase();
throw new Error(
`Create ${collectionName} failed: ${collectionName} with id ${params.collectionId} already exists`
);
}
}
const { apiType } = userContext; const { apiType } = userContext;
switch (apiType) { switch (apiType) {
case "SQL": case "SQL":
@@ -83,6 +80,23 @@ const createCollectionWithARM = async (params: DataModels.CreateCollectionParams
}; };
const createSqlContainer = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => { const createSqlContainer = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
try {
const getResponse = await getSqlContainer(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
params.collectionId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create container failed: container with id ${params.collectionId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params); const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
const resource: ARMTypes.SqlContainerResource = { const resource: ARMTypes.SqlContainerResource = {
id: params.collectionId, id: params.collectionId,
@@ -120,6 +134,23 @@ const createSqlContainer = async (params: DataModels.CreateCollectionParams): Pr
const createMongoCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => { const createMongoCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
const mongoWildcardIndexOnAllFields: ARMTypes.MongoIndex[] = [{ key: { keys: ["$**"] } }, { key: { keys: ["_id"] } }]; const mongoWildcardIndexOnAllFields: ARMTypes.MongoIndex[] = [{ key: { keys: ["$**"] } }, { key: { keys: ["_id"] } }];
try {
const getResponse = await getMongoDBCollection(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
params.collectionId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create collection failed: collection with id ${params.collectionId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params); const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
const resource: ARMTypes.MongoDBCollectionResource = { const resource: ARMTypes.MongoDBCollectionResource = {
id: params.collectionId, id: params.collectionId,
@@ -161,6 +192,23 @@ const createMongoCollection = async (params: DataModels.CreateCollectionParams):
}; };
const createCassandraTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => { const createCassandraTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
try {
const getResponse = await getCassandraTable(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
params.collectionId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create table failed: table with id ${params.collectionId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params); const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
const resource: ARMTypes.CassandraTableResource = { const resource: ARMTypes.CassandraTableResource = {
id: params.collectionId, id: params.collectionId,
@@ -188,6 +236,23 @@ const createCassandraTable = async (params: DataModels.CreateCollectionParams):
}; };
const createGraph = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => { const createGraph = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
try {
const getResponse = await getGremlinGraph(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
params.collectionId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create graph failed: graph with id ${params.collectionId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params); const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
const resource: ARMTypes.GremlinGraphResource = { const resource: ARMTypes.GremlinGraphResource = {
id: params.collectionId, id: params.collectionId,
@@ -222,6 +287,22 @@ const createGraph = async (params: DataModels.CreateCollectionParams): Promise<D
}; };
const createTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => { const createTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
try {
const getResponse = await getTable(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.collectionId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create table failed: table with id ${params.collectionId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params); const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
const resource: ARMTypes.TableResource = { const resource: ARMTypes.TableResource = {
id: params.collectionId, id: params.collectionId,

View File

@@ -2,20 +2,27 @@ import { DatabaseResponse } from "@azure/cosmos";
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest"; import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import { useDatabases } from "../../Explorer/useDatabases";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { getDatabaseName } from "../../Utils/APITypeUtils"; import {
import { createUpdateCassandraKeyspace } from "../../Utils/arm/generatedClients/cosmos/cassandraResources"; createUpdateCassandraKeyspace,
import { createUpdateGremlinDatabase } from "../../Utils/arm/generatedClients/cosmos/gremlinResources"; getCassandraKeyspace,
import { createUpdateMongoDBDatabase } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources"; } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { createUpdateSqlDatabase } from "../../Utils/arm/generatedClients/cosmos/sqlResources"; import {
createUpdateGremlinDatabase,
getGremlinDatabase,
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import {
createUpdateMongoDBDatabase,
getMongoDBDatabase,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { createUpdateSqlDatabase, getSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { import {
CassandraKeyspaceCreateUpdateParameters, CassandraKeyspaceCreateUpdateParameters,
CreateUpdateOptions, CreateUpdateOptions,
GremlinDatabaseCreateUpdateParameters, GremlinDatabaseCreateUpdateParameters,
MongoDBDatabaseCreateUpdateParameters, MongoDBDatabaseCreateUpdateParameters,
SqlDatabaseCreateUpdateParameters, SqlDatabaseCreateUpdateParameters,
} from "../../Utils/arm/generatedClients/cosmos/types"; } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
@@ -41,11 +48,6 @@ export async function createDatabase(params: DataModels.CreateDatabaseParams): P
} }
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> { async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
if (!useDatabases.getState().validateDatabaseId(params.databaseId)) {
const databaseName = getDatabaseName().toLocaleLowerCase();
throw new Error(`Create ${databaseName} failed: ${databaseName} with id ${params.databaseId} already exists`);
}
const { apiType } = userContext; const { apiType } = userContext;
switch (apiType) { switch (apiType) {
@@ -63,6 +65,22 @@ async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): P
} }
async function createSqlDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> { async function createSqlDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
try {
const getResponse = await getSqlDatabase(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: CreateUpdateOptions = constructRpOptions(params); const options: CreateUpdateOptions = constructRpOptions(params);
const rpPayload: SqlDatabaseCreateUpdateParameters = { const rpPayload: SqlDatabaseCreateUpdateParameters = {
properties: { properties: {
@@ -83,6 +101,22 @@ async function createSqlDatabase(params: DataModels.CreateDatabaseParams): Promi
} }
async function createMongoDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> { async function createMongoDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
try {
const getResponse = await getMongoDBDatabase(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: CreateUpdateOptions = constructRpOptions(params); const options: CreateUpdateOptions = constructRpOptions(params);
const rpPayload: MongoDBDatabaseCreateUpdateParameters = { const rpPayload: MongoDBDatabaseCreateUpdateParameters = {
properties: { properties: {
@@ -103,6 +137,22 @@ async function createMongoDatabase(params: DataModels.CreateDatabaseParams): Pro
} }
async function createCassandraKeyspace(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> { async function createCassandraKeyspace(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
try {
const getResponse = await getCassandraKeyspace(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: CreateUpdateOptions = constructRpOptions(params); const options: CreateUpdateOptions = constructRpOptions(params);
const rpPayload: CassandraKeyspaceCreateUpdateParameters = { const rpPayload: CassandraKeyspaceCreateUpdateParameters = {
properties: { properties: {
@@ -123,6 +173,22 @@ async function createCassandraKeyspace(params: DataModels.CreateDatabaseParams):
} }
async function createGremlineDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> { async function createGremlineDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
try {
const getResponse = await getGremlinDatabase(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: CreateUpdateOptions = constructRpOptions(params); const options: CreateUpdateOptions = constructRpOptions(params);
const rpPayload: GremlinDatabaseCreateUpdateParameters = { const rpPayload: GremlinDatabaseCreateUpdateParameters = {
properties: { properties: {

View File

@@ -4,11 +4,11 @@ import { userContext } from "../../UserContext";
import { import {
createUpdateSqlStoredProcedure, createUpdateSqlStoredProcedure,
getSqlStoredProcedure, getSqlStoredProcedure,
} from "../../Utils/arm/generatedClients/cosmos/sqlResources"; } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { import {
SqlStoredProcedureCreateUpdateParameters, SqlStoredProcedureCreateUpdateParameters,
SqlStoredProcedureResource, SqlStoredProcedureResource,
} from "../../Utils/arm/generatedClients/cosmos/types"; } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";

View File

@@ -1,8 +1,11 @@
import { TriggerDefinition } from "@azure/cosmos"; import { Resource, TriggerDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/cosmos/sqlResources"; import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { SqlTriggerCreateUpdateParameters, SqlTriggerResource } from "../../Utils/arm/generatedClients/cosmos/types"; import {
SqlTriggerCreateUpdateParameters,
SqlTriggerResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
@@ -10,8 +13,8 @@ import { handleError } from "../ErrorHandlingUtils";
export async function createTrigger( export async function createTrigger(
databaseId: string, databaseId: string,
collectionId: string, collectionId: string,
trigger: SqlTriggerResource trigger: TriggerDefinition
): Promise<TriggerDefinition | SqlTriggerResource> { ): Promise<TriggerDefinition & Resource> {
const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`); const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`);
try { try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") { if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
@@ -35,7 +38,7 @@ export async function createTrigger(
const createTriggerParams: SqlTriggerCreateUpdateParameters = { const createTriggerParams: SqlTriggerCreateUpdateParameters = {
properties: { properties: {
resource: trigger, resource: trigger as SqlTriggerResource,
options: {}, options: {},
}, },
}; };
@@ -48,13 +51,10 @@ export async function createTrigger(
trigger.id, trigger.id,
createTriggerParams createTriggerParams
); );
return rpResponse && rpResponse.properties?.resource; return rpResponse && (rpResponse.properties?.resource as TriggerDefinition & Resource);
} }
const response = await client() const response = await client().database(databaseId).container(collectionId).scripts.triggers.create(trigger);
.database(databaseId)
.container(collectionId)
.scripts.triggers.create((trigger as unknown) as TriggerDefinition); // TODO: TypeScript does not like the SQL SDK trigger type
return response.resource; return response.resource;
} catch (error) { } catch (error) {
handleError(error, "CreateTrigger", `Error while creating trigger ${trigger.id}`); handleError(error, "CreateTrigger", `Error while creating trigger ${trigger.id}`);

View File

@@ -4,11 +4,11 @@ import { userContext } from "../../UserContext";
import { import {
createUpdateSqlUserDefinedFunction, createUpdateSqlUserDefinedFunction,
getSqlUserDefinedFunction, getSqlUserDefinedFunction,
} from "../../Utils/arm/generatedClients/cosmos/sqlResources"; } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { import {
SqlUserDefinedFunctionCreateUpdateParameters, SqlUserDefinedFunctionCreateUpdateParameters,
SqlUserDefinedFunctionResource, SqlUserDefinedFunctionResource,
} from "../../Utils/arm/generatedClients/cosmos/types"; } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";

View File

@@ -1,10 +1,10 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { deleteCassandraTable } from "../../Utils/arm/generatedClients/cosmos/cassandraResources"; import { deleteCassandraTable } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/cosmos/gremlinResources"; import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources"; import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { deleteSqlContainer } from "../../Utils/arm/generatedClients/cosmos/sqlResources"; import { deleteSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { deleteTable } from "../../Utils/arm/generatedClients/cosmos/tableResources"; import { deleteTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";

View File

@@ -1,9 +1,9 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/cosmos/cassandraResources"; import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/cosmos/gremlinResources"; import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources"; import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { deleteSqlDatabase } from "../../Utils/arm/generatedClients/cosmos/sqlResources"; import { deleteSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";

View File

@@ -1,6 +1,6 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { deleteSqlStoredProcedure } from "../../Utils/arm/generatedClients/cosmos/sqlResources"; import { deleteSqlStoredProcedure } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";

View File

@@ -1,6 +1,6 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { deleteSqlTrigger } from "../../Utils/arm/generatedClients/cosmos/sqlResources"; import { deleteSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";

View File

@@ -1,6 +1,6 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { deleteSqlUserDefinedFunction } from "../../Utils/arm/generatedClients/cosmos/sqlResources"; import { deleteSqlUserDefinedFunction } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";

View File

@@ -1,11 +1,11 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { Offer, ReadCollectionOfferParams } from "../../Contracts/DataModels"; import { Offer, ReadCollectionOfferParams } from "../../Contracts/DataModels";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { getCassandraTableThroughput } from "../../Utils/arm/generatedClients/cosmos/cassandraResources"; import { getCassandraTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { getGremlinGraphThroughput } from "../../Utils/arm/generatedClients/cosmos/gremlinResources"; import { getGremlinGraphThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { getMongoDBCollectionThroughput } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources"; import { getMongoDBCollectionThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { getSqlContainerThroughput } from "../../Utils/arm/generatedClients/cosmos/sqlResources"; import { getSqlContainerThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { getTableThroughput } from "../../Utils/arm/generatedClients/cosmos/tableResources"; import { getTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
import { readOfferWithSDK } from "./readOfferWithSDK"; import { readOfferWithSDK } from "./readOfferWithSDK";

View File

@@ -1,11 +1,11 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { listCassandraTables } from "../../Utils/arm/generatedClients/cosmos/cassandraResources"; import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/cosmos/gremlinResources"; import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources"; import { listMongoDBCollections } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { listSqlContainers } from "../../Utils/arm/generatedClients/cosmos/sqlResources"; import { listSqlContainers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { listTables } from "../../Utils/arm/generatedClients/cosmos/tableResources"; import { listTables } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";

View File

@@ -1,10 +1,10 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { Offer, ReadDatabaseOfferParams } from "../../Contracts/DataModels"; import { Offer, ReadDatabaseOfferParams } from "../../Contracts/DataModels";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { getCassandraKeyspaceThroughput } from "../../Utils/arm/generatedClients/cosmos/cassandraResources"; import { getCassandraKeyspaceThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { getGremlinDatabaseThroughput } from "../../Utils/arm/generatedClients/cosmos/gremlinResources"; import { getGremlinDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { getMongoDBDatabaseThroughput } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources"; import { getMongoDBDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { getSqlDatabaseThroughput } from "../../Utils/arm/generatedClients/cosmos/sqlResources"; import { getSqlDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
import { readOfferWithSDK } from "./readOfferWithSDK"; import { readOfferWithSDK } from "./readOfferWithSDK";

View File

@@ -1,10 +1,10 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/cosmos/cassandraResources"; import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/cosmos/gremlinResources"; import { listGremlinDatabases } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources"; import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { listSqlDatabases } from "../../Utils/arm/generatedClients/cosmos/sqlResources"; import { listSqlDatabases } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";

View File

@@ -1,7 +1,7 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { getMongoDBCollection } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources"; import { getMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { MongoDBCollectionResource } from "../../Utils/arm/generatedClients/cosmos/types"; import { MongoDBCollectionResource } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";

View File

@@ -1,7 +1,7 @@
import { Resource, StoredProcedureDefinition } from "@azure/cosmos"; import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { listSqlStoredProcedures } from "../../Utils/arm/generatedClients/cosmos/sqlResources"; import { listSqlStoredProcedures } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";

View File

@@ -1,8 +1,7 @@
import { TriggerDefinition } from "@azure/cosmos"; import { Resource, TriggerDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { listSqlTriggers } from "../../Utils/arm/generatedClients/cosmos/sqlResources"; import { listSqlTriggers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { SqlTriggerResource } from "../../Utils/arm/generatedClients/cosmos/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
@@ -10,7 +9,7 @@ import { handleError } from "../ErrorHandlingUtils";
export async function readTriggers( export async function readTriggers(
databaseId: string, databaseId: string,
collectionId: string collectionId: string
): Promise<SqlTriggerResource[] | TriggerDefinition[]> { ): Promise<(TriggerDefinition & Resource)[]> {
const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`); const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`);
try { try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") { if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
@@ -21,7 +20,7 @@ export async function readTriggers(
databaseId, databaseId,
collectionId collectionId
); );
return rpResponse?.value?.map((trigger) => trigger.properties?.resource); return rpResponse?.value?.map((trigger) => trigger.properties?.resource as TriggerDefinition & Resource);
} }
const response = await client().database(databaseId).container(collectionId).scripts.triggers.readAll().fetchAll(); const response = await client().database(databaseId).container(collectionId).scripts.triggers.readAll().fetchAll();

View File

@@ -1,7 +1,7 @@
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos"; import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { listSqlUserDefinedFunctions } from "../../Utils/arm/generatedClients/cosmos/sqlResources"; import { listSqlUserDefinedFunctions } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";

View File

@@ -6,20 +6,23 @@ import { userContext } from "../../UserContext";
import { import {
createUpdateCassandraTable, createUpdateCassandraTable,
getCassandraTable, getCassandraTable,
} from "../../Utils/arm/generatedClients/cosmos/cassandraResources"; } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { createUpdateGremlinGraph, getGremlinGraph } from "../../Utils/arm/generatedClients/cosmos/gremlinResources"; import {
createUpdateGremlinGraph,
getGremlinGraph,
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { import {
createUpdateMongoDBCollection, createUpdateMongoDBCollection,
getMongoDBCollection, getMongoDBCollection,
} from "../../Utils/arm/generatedClients/cosmos/mongoDBResources"; } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/cosmos/sqlResources"; import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/cosmos/tableResources"; import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { import {
ExtendedResourceProperties, ExtendedResourceProperties,
MongoDBCollectionCreateUpdateParameters, MongoDBCollectionCreateUpdateParameters,
SqlContainerCreateUpdateParameters, SqlContainerCreateUpdateParameters,
SqlContainerResource, SqlContainerResource,
} from "../../Utils/arm/generatedClients/cosmos/types"; } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";

View File

@@ -10,7 +10,7 @@ import {
migrateCassandraTableToManualThroughput, migrateCassandraTableToManualThroughput,
updateCassandraKeyspaceThroughput, updateCassandraKeyspaceThroughput,
updateCassandraTableThroughput, updateCassandraTableThroughput,
} from "../../Utils/arm/generatedClients/cosmos/cassandraResources"; } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { import {
migrateGremlinDatabaseToAutoscale, migrateGremlinDatabaseToAutoscale,
migrateGremlinDatabaseToManualThroughput, migrateGremlinDatabaseToManualThroughput,
@@ -18,7 +18,7 @@ import {
migrateGremlinGraphToManualThroughput, migrateGremlinGraphToManualThroughput,
updateGremlinDatabaseThroughput, updateGremlinDatabaseThroughput,
updateGremlinGraphThroughput, updateGremlinGraphThroughput,
} from "../../Utils/arm/generatedClients/cosmos/gremlinResources"; } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { import {
migrateMongoDBCollectionToAutoscale, migrateMongoDBCollectionToAutoscale,
migrateMongoDBCollectionToManualThroughput, migrateMongoDBCollectionToManualThroughput,
@@ -26,7 +26,7 @@ import {
migrateMongoDBDatabaseToManualThroughput, migrateMongoDBDatabaseToManualThroughput,
updateMongoDBCollectionThroughput, updateMongoDBCollectionThroughput,
updateMongoDBDatabaseThroughput, updateMongoDBDatabaseThroughput,
} from "../../Utils/arm/generatedClients/cosmos/mongoDBResources"; } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { import {
migrateSqlContainerToAutoscale, migrateSqlContainerToAutoscale,
migrateSqlContainerToManualThroughput, migrateSqlContainerToManualThroughput,
@@ -34,13 +34,13 @@ import {
migrateSqlDatabaseToManualThroughput, migrateSqlDatabaseToManualThroughput,
updateSqlContainerThroughput, updateSqlContainerThroughput,
updateSqlDatabaseThroughput, updateSqlDatabaseThroughput,
} from "../../Utils/arm/generatedClients/cosmos/sqlResources"; } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { import {
migrateTableToAutoscale, migrateTableToAutoscale,
migrateTableToManualThroughput, migrateTableToManualThroughput,
updateTableThroughput, updateTableThroughput,
} from "../../Utils/arm/generatedClients/cosmos/tableResources"; } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/cosmos/types"; import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { HttpHeaders } from "../Constants"; import { HttpHeaders } from "../Constants";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";

View File

@@ -4,11 +4,11 @@ import { userContext } from "../../UserContext";
import { import {
createUpdateSqlStoredProcedure, createUpdateSqlStoredProcedure,
getSqlStoredProcedure, getSqlStoredProcedure,
} from "../../Utils/arm/generatedClients/cosmos/sqlResources"; } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { import {
SqlStoredProcedureCreateUpdateParameters, SqlStoredProcedureCreateUpdateParameters,
SqlStoredProcedureResource, SqlStoredProcedureResource,
} from "../../Utils/arm/generatedClients/cosmos/types"; } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";

View File

@@ -1,8 +1,11 @@
import { TriggerDefinition } from "@azure/cosmos"; import { TriggerDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/cosmos/sqlResources"; import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { SqlTriggerCreateUpdateParameters, SqlTriggerResource } from "../../Utils/arm/generatedClients/cosmos/types"; import {
SqlTriggerCreateUpdateParameters,
SqlTriggerResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
@@ -10,8 +13,8 @@ import { handleError } from "../ErrorHandlingUtils";
export async function updateTrigger( export async function updateTrigger(
databaseId: string, databaseId: string,
collectionId: string, collectionId: string,
trigger: SqlTriggerResource trigger: TriggerDefinition
): Promise<SqlTriggerResource | TriggerDefinition> { ): Promise<TriggerDefinition> {
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`); const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`);
const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext; const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
try { try {
@@ -28,7 +31,7 @@ export async function updateTrigger(
if (getResponse?.properties?.resource) { if (getResponse?.properties?.resource) {
const createTriggerParams: SqlTriggerCreateUpdateParameters = { const createTriggerParams: SqlTriggerCreateUpdateParameters = {
properties: { properties: {
resource: trigger, resource: trigger as SqlTriggerResource,
options: {}, options: {},
}, },
}; };
@@ -41,7 +44,7 @@ export async function updateTrigger(
trigger.id, trigger.id,
createTriggerParams createTriggerParams
); );
return rpResponse && rpResponse.properties?.resource; return rpResponse && (rpResponse.properties?.resource as TriggerDefinition);
} }
throw new Error(`Failed to update trigger: ${trigger.id} does not exist.`); throw new Error(`Failed to update trigger: ${trigger.id} does not exist.`);
@@ -51,7 +54,7 @@ export async function updateTrigger(
.database(databaseId) .database(databaseId)
.container(collectionId) .container(collectionId)
.scripts.trigger(trigger.id) .scripts.trigger(trigger.id)
.replace((trigger as unknown) as TriggerDefinition); // TODO: TypeScript does not like the SQL SDK trigger type .replace(trigger);
return response?.resource; return response?.resource;
} catch (error) { } catch (error) {
handleError(error, "UpdateTrigger", `Error while updating trigger ${trigger.id}`); handleError(error, "UpdateTrigger", `Error while updating trigger ${trigger.id}`);

View File

@@ -4,11 +4,11 @@ import { userContext } from "../../UserContext";
import { import {
createUpdateSqlUserDefinedFunction, createUpdateSqlUserDefinedFunction,
getSqlUserDefinedFunction, getSqlUserDefinedFunction,
} from "../../Utils/arm/generatedClients/cosmos/sqlResources"; } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { import {
SqlUserDefinedFunctionCreateUpdateParameters, SqlUserDefinedFunctionCreateUpdateParameters,
SqlUserDefinedFunctionResource, SqlUserDefinedFunctionResource,
} from "../../Utils/arm/generatedClients/cosmos/types"; } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";

View File

@@ -27,7 +27,6 @@ export interface ConfigContext {
hostedExplorerURL: string; hostedExplorerURL: string;
armAPIVersion?: string; armAPIVersion?: string;
allowedJunoOrigins: string[]; allowedJunoOrigins: string[];
msalRedirectURI?: string;
} }
// Default configuration // Default configuration
@@ -118,14 +117,6 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
const armAPIVersion = params.get("armAPIVersion") || ""; const armAPIVersion = params.get("armAPIVersion") || "";
updateConfigContext({ armAPIVersion }); updateConfigContext({ armAPIVersion });
} }
if (params.has("armEndpoint")) {
const ARM_ENDPOINT = params.get("armEndpoint") || "";
updateConfigContext({ ARM_ENDPOINT });
}
if (params.has("aadEndpoint")) {
const AAD_ENDPOINT = params.get("aadEndpoint") || "";
updateConfigContext({ AAD_ENDPOINT });
}
if (params.has("platform")) { if (params.has("platform")) {
const platform = params.get("platform"); const platform = params.get("platform");
switch (platform) { switch (platform) {

View File

@@ -9,7 +9,6 @@ export interface DatabaseAccount {
export interface DatabaseAccountExtendedProperties { export interface DatabaseAccountExtendedProperties {
documentEndpoint?: string; documentEndpoint?: string;
disableLocalAuth?: boolean;
tableEndpoint?: string; tableEndpoint?: string;
gremlinEndpoint?: string; gremlinEndpoint?: string;
cassandraEndpoint?: string; cassandraEndpoint?: string;
@@ -23,7 +22,6 @@ export interface DatabaseAccountExtendedProperties {
enableAnalyticalStorage?: boolean; enableAnalyticalStorage?: boolean;
isVirtualNetworkFilterEnabled?: boolean; isVirtualNetworkFilterEnabled?: boolean;
ipRules?: IpRule[]; ipRules?: IpRule[];
privateEndpointConnections?: unknown[];
} }
export interface DatabaseAccountResponseLocation { export interface DatabaseAccountResponseLocation {
@@ -169,7 +167,7 @@ export interface KeyResource {
export interface IndexingPolicy { export interface IndexingPolicy {
automatic: boolean; automatic: boolean;
indexingMode: "consistent" | "lazy" | "none"; indexingMode: string;
includedPaths: any; includedPaths: any;
excludedPaths: any; excludedPaths: any;
compositeIndexes?: any; compositeIndexes?: any;
@@ -178,7 +176,7 @@ export interface IndexingPolicy {
export interface PartitionKey { export interface PartitionKey {
paths: string[]; paths: string[];
kind: "Hash" | "Range" | "MultiHash"; kind: string;
version: number; version: number;
systemKey?: boolean; systemKey?: boolean;
} }
@@ -393,6 +391,16 @@ export interface GeospatialConfig {
type: string; type: string;
} }
export interface GatewayDatabaseAccount {
MediaLink: string;
DatabasesLink: string;
MaxMediaStorageUsageInMB: number;
CurrentMediaStorageUsageInMB: number;
EnableMultipleWriteLocations?: boolean;
WritableLocations: RegionEndpoint[];
ReadableLocations: RegionEndpoint[];
}
export interface RegionEndpoint { export interface RegionEndpoint {
name: string; name: string;
documentAccountEndpoint: string; documentAccountEndpoint: string;
@@ -413,6 +421,13 @@ export interface AccountKeys {
secondaryReadonlyMasterKey: string; secondaryReadonlyMasterKey: string;
} }
export interface AfecFeature {
id: string;
name: string;
properties: { state: string };
type: string;
}
export interface OperationStatus { export interface OperationStatus {
status: string; status: string;
id?: string; id?: string;
@@ -492,6 +507,91 @@ export interface MongoParameters extends RpParameters {
analyticalStorageTtl?: number; analyticalStorageTtl?: number;
} }
export interface SparkClusterLibrary {
name: string;
}
export interface Library extends SparkClusterLibrary {
properties: {
kind: "Jar";
source: {
kind: "HttpsUri";
uri: string;
libraryFileName: string;
};
};
}
export interface LibraryFeedResponse {
value: Library[];
}
export interface ArmResource {
id: string;
location: string;
name: string;
type: string;
tags: { [key: string]: string };
}
export interface ArcadiaWorkspaceIdentity {
type: string;
principalId: string;
tenantId: string;
}
export interface ArcadiaWorkspaceProperties {
managedResourceGroupName: string;
provisioningState: string;
sqlAdministratorLogin: string;
connectivityEndpoints: {
artifacts: string;
dev: string;
spark: string;
sql: string;
web: string;
};
defaultDataLakeStorage: {
accountUrl: string;
filesystem: string;
};
}
export interface ArcadiaWorkspaceFeedResponse {
value: ArcadiaWorkspace[];
}
export interface ArcadiaWorkspace extends ArmResource {
identity: ArcadiaWorkspaceIdentity;
properties: ArcadiaWorkspaceProperties;
}
export interface SparkPoolFeedResponse {
value: SparkPool[];
}
export interface SparkPoolProperties {
creationDate: string;
sparkVersion: string;
nodeCount: number;
nodeSize: string;
nodeSizeFamily: string;
provisioningState: string;
autoScale: {
enabled: boolean;
minNodeCount: number;
maxNodeCount: number;
};
autoPause: {
enabled: boolean;
delayInMinutes: number;
};
}
export interface SparkPool extends ArmResource {
properties: SparkPoolProperties;
}
export interface MemoryUsageInfo { export interface MemoryUsageInfo {
freeKB: number; freeKB: number;
totalKB: number; totalKB: number;

View File

@@ -5,8 +5,9 @@ import {
TriggerDefinition, TriggerDefinition,
UserDefinedFunctionDefinition, UserDefinedFunctionDefinition,
} from "@azure/cosmos"; } from "@azure/cosmos";
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
import Explorer from "../Explorer/Explorer"; import Explorer from "../Explorer/Explorer";
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/ConsoleData"; import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient"; import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient";
import ConflictId from "../Explorer/Tree/ConflictId"; import ConflictId from "../Explorer/Tree/ConflictId";
import DocumentId from "../Explorer/Tree/DocumentId"; import DocumentId from "../Explorer/Tree/DocumentId";
@@ -14,8 +15,6 @@ import StoredProcedure from "../Explorer/Tree/StoredProcedure";
import Trigger from "../Explorer/Tree/Trigger"; import Trigger from "../Explorer/Tree/Trigger";
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction"; import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
import { SelfServeType } from "../SelfServe/SelfServeUtils"; import { SelfServeType } from "../SelfServe/SelfServeUtils";
import { CollectionCreationDefaults } from "../UserContext";
import { SqlTriggerResource } from "../Utils/arm/generatedClients/cosmos/types";
import * as DataModels from "./DataModels"; import * as DataModels from "./DataModels";
import { SubscriptionType } from "./SubscriptionType"; import { SubscriptionType } from "./SubscriptionType";
@@ -89,6 +88,7 @@ export interface Database extends TreeNode {
selectedSubnodeKind: ko.Observable<CollectionTabKind>; selectedSubnodeKind: ko.Observable<CollectionTabKind>;
selectDatabase(): void;
expandDatabase(): Promise<void>; expandDatabase(): Promise<void>;
collapseDatabase(): void; collapseDatabase(): void;
@@ -175,7 +175,7 @@ export interface Collection extends CollectionBase {
createStoredProcedureNode(data: StoredProcedureDefinition & Resource): StoredProcedure; createStoredProcedureNode(data: StoredProcedureDefinition & Resource): StoredProcedure;
createUserDefinedFunctionNode(data: UserDefinedFunctionDefinition & Resource): UserDefinedFunction; createUserDefinedFunctionNode(data: UserDefinedFunctionDefinition & Resource): UserDefinedFunction;
createTriggerNode(data: TriggerDefinition | SqlTriggerResource): Trigger; createTriggerNode(data: TriggerDefinition & Resource): Trigger;
findStoredProcedureWithId(sprocRid: string): StoredProcedure; findStoredProcedureWithId(sprocRid: string): StoredProcedure;
findTriggerWithId(triggerRid: string): Trigger; findTriggerWithId(triggerRid: string): Trigger;
findUserDefinedFunctionWithId(udfRid: string): UserDefinedFunction; findUserDefinedFunctionWithId(udfRid: string): UserDefinedFunction;
@@ -274,6 +274,8 @@ export interface TabOptions {
tabKind: CollectionTabKind; tabKind: CollectionTabKind;
title: string; title: string;
tabPath: string; tabPath: string;
hashLocation: string;
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]) => void;
isTabsContentExpanded?: ko.Observable<boolean>; isTabsContentExpanded?: ko.Observable<boolean>;
onLoadStartKey?: number; onLoadStartKey?: number;
@@ -284,7 +286,6 @@ export interface TabOptions {
rid?: string; rid?: string;
node?: TreeNode; node?: TreeNode;
theme?: string; theme?: string;
index?: number;
} }
export interface DocumentsTabOptions extends TabOptions { export interface DocumentsTabOptions extends TabOptions {
@@ -409,6 +410,25 @@ export interface SelfServeFrameInputs {
flights?: readonly string[]; flights?: readonly string[];
} }
export interface CollectionCreationDefaults {
storage: string;
throughput: ThroughputDefaults;
}
export interface ThroughputDefaults {
fixed: number;
unlimited:
| number
| {
collectionThreshold: number;
lessThanOrEqualToThreshold: number;
greatThanThreshold: number;
};
unlimitedmax: number;
unlimitedmin: number;
shared: number;
}
export class MonacoEditorSettings { export class MonacoEditorSettings {
public readonly language: string; public readonly language: string;
public readonly readOnly: boolean; public readonly readOnly: boolean;

View File

@@ -0,0 +1,14 @@
jest.mock("monaco-editor");
import * as ko from "knockout";
import "./ComponentRegisterer";
describe("Component Registerer", () => {
it("should register json-editor component", () => {
expect(ko.components.isRegistered("json-editor")).toBe(true);
});
it("should register dynamic-list component", () => {
expect(ko.components.isRegistered("dynamic-list")).toBe(true);
});
});

View File

@@ -1,8 +1,12 @@
import * as ko from "knockout"; import * as ko from "knockout";
import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent"; import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent";
import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent";
import { EditorComponent } from "./Controls/Editor/EditorComponent"; import { EditorComponent } from "./Controls/Editor/EditorComponent";
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent"; import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
ko.components.register("editor", new EditorComponent()); ko.components.register("editor", new EditorComponent());
ko.components.register("json-editor", new JsonEditorComponent()); ko.components.register("json-editor", new JsonEditorComponent());
ko.components.register("diff-editor", new DiffEditorComponent()); ko.components.register("diff-editor", new DiffEditorComponent());
ko.components.register("dynamic-list", DynamicListComponent);
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);

View File

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

View File

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

View File

@@ -3,14 +3,13 @@
*/ */
import * as React from "react"; import * as React from "react";
import * as Constants from "../../../Common/Constants";
import AnimateHeight from "react-animate-height"; import AnimateHeight from "react-animate-height";
import TriangleDownIcon from "../../../../images/Triangle-down.svg"; import TriangleDownIcon from "../../../../images/Triangle-down.svg";
import TriangleRightIcon from "../../../../images/Triangle-right.svg"; import TriangleRightIcon from "../../../../images/Triangle-right.svg";
import * as Constants from "../../../Common/Constants";
export interface AccordionComponentProps { export interface AccordionComponentProps {}
children: React.ReactNode;
}
export class AccordionComponent extends React.Component<AccordionComponentProps> { export class AccordionComponent extends React.Component<AccordionComponentProps> {
public render(): JSX.Element { public render(): JSX.Element {
@@ -28,12 +27,12 @@ export interface AccordionItemComponentProps {
} }
interface AccordionItemComponentState { interface AccordionItemComponentState {
isExpanded?: boolean; isExpanded: boolean;
} }
export class AccordionItemComponent extends React.Component<AccordionItemComponentProps, AccordionItemComponentState> { export class AccordionItemComponent extends React.Component<AccordionItemComponentProps, AccordionItemComponentState> {
private static readonly durationMS = 500; private static readonly durationMS = 500;
private isExpanded?: boolean; private isExpanded: boolean;
constructor(props: AccordionItemComponentProps) { constructor(props: AccordionItemComponentProps) {
super(props); super(props);
@@ -80,7 +79,7 @@ export class AccordionItemComponent extends React.Component<AccordionItemCompone
); );
} }
private onHeaderClick = (): void => { private onHeaderClick = (event: React.MouseEvent<HTMLDivElement>): void => {
this.setState({ isExpanded: !this.state.isExpanded }); this.setState({ isExpanded: !this.state.isExpanded });
}; };

View File

@@ -0,0 +1,142 @@
import { DefaultButton, IButtonStyles, IContextualMenuItem, IContextualMenuProps } from "@fluentui/react";
import * as React from "react";
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
import * as Logger from "../../../Common/Logger";
import { ArcadiaWorkspace, SparkPool } from "../../../Contracts/DataModels";
export interface ArcadiaMenuPickerProps {
selectText?: string;
disableSubmenu?: boolean;
selectedSparkPool: string;
workspaces: ArcadiaWorkspaceItem[];
onSparkPoolSelect: (
e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
item: IContextualMenuItem
) => boolean | void;
onCreateNewWorkspaceClicked: () => boolean | void;
onCreateNewSparkPoolClicked: (workspaceResourceId: string) => boolean | void;
}
interface ArcadiaMenuPickerStates {
selectedSparkPool: string;
}
export interface ArcadiaWorkspaceItem extends ArcadiaWorkspace {
sparkPools: SparkPool[];
}
export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, ArcadiaMenuPickerStates> {
constructor(props: ArcadiaMenuPickerProps) {
super(props);
this.state = {
selectedSparkPool: props.selectedSparkPool,
};
}
private _onSparkPoolClicked = (
e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
item: IContextualMenuItem
): boolean | void => {
try {
this.props.onSparkPoolSelect(e, item);
this.setState({
selectedSparkPool: item.text,
});
} catch (error) {
Logger.logError(getErrorMessage(error), "ArcadiaMenuPicker/_onSparkPoolClicked");
throw error;
}
};
private _onCreateNewWorkspaceClicked = (
e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
item: IContextualMenuItem
): boolean | void => {
this.props.onCreateNewWorkspaceClicked();
};
private _onCreateNewSparkPoolClicked = (
e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
item: IContextualMenuItem
): boolean | void => {
this.props.onCreateNewSparkPoolClicked(item.key);
};
public render() {
const { workspaces } = this.props;
let workspaceMenuItems: IContextualMenuItem[] = workspaces.map((workspace) => {
let sparkPoolsMenuProps: IContextualMenuProps = {
items: workspace.sparkPools.map(
(sparkpool): IContextualMenuItem => ({
key: sparkpool.id,
text: sparkpool.name,
onClick: this._onSparkPoolClicked,
})
),
};
if (!sparkPoolsMenuProps.items.length) {
sparkPoolsMenuProps.items.push({
key: workspace.id,
text: "Create new spark pool",
onClick: this._onCreateNewSparkPoolClicked,
});
}
return {
key: workspace.id,
text: workspace.name,
subMenuProps: this.props.disableSubmenu ? undefined : sparkPoolsMenuProps,
};
});
if (!workspaceMenuItems.length) {
workspaceMenuItems.push({
key: "create_workspace",
text: "Create new workspace",
onClick: this._onCreateNewWorkspaceClicked,
});
}
const dropdownStyle: IButtonStyles = {
root: {
backgroundColor: "transparent",
margin: "auto 5px",
padding: "0",
border: "0",
},
rootHovered: {
backgroundColor: "transparent",
},
rootChecked: {
backgroundColor: "transparent",
},
rootFocused: {
backgroundColor: "transparent",
},
rootExpanded: {
backgroundColor: "transparent",
},
flexContainer: {
height: "30px",
border: "1px solid #a6a6a6",
padding: "0 8px",
},
label: {
fontWeight: "400",
fontSize: "12px",
},
};
return (
<DefaultButton
text={this.state.selectedSparkPool || this.props.selectText || "Select a Spark pool"}
persistMenu={true}
className="arcadia-menu-picker"
menuProps={{
items: workspaceMenuItems,
}}
styles={dropdownStyle}
/>
);
}
}

View File

@@ -1,12 +1,15 @@
import * as StringUtils from "../../../Utils/StringUtils";
import { KeyCodes } from "../../../Common/Constants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
/** /**
* React component for Command button component. * React component for Command button component.
*/ */
import * as React from "react"; import * as React from "react";
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png"; import { ArcadiaMenuPickerProps } from "../Arcadia/ArcadiaMenuPicker";
import { KeyCodes } from "../../../Common/Constants";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import * as StringUtils from "../../../Utils/StringUtils";
/** /**
* Options for this component * Options for this component
@@ -111,6 +114,15 @@ export interface CommandButtonComponentProps {
* Aria-label for the button * Aria-label for the button
*/ */
ariaLabel: string; ariaLabel: string;
//TODO: generalize customized command bar
/**
* If set to true, will render arcadia picker
*/
isArcadiaPicker?: boolean;
/**
* props to render arcadia picker
*/
arcadiaProps?: ArcadiaMenuPickerProps;
} }
export class CommandButtonComponent extends React.Component<CommandButtonComponentProps> { export class CommandButtonComponent extends React.Component<CommandButtonComponentProps> {
@@ -121,7 +133,8 @@ export class CommandButtonComponent extends React.Component<CommandButtonCompone
if (!this.dropdownElt || !this.expandButtonElt) { if (!this.dropdownElt || !this.expandButtonElt) {
return; return;
} }
$(this.dropdownElt).offset({ left: $(this.expandButtonElt).offset().left });
const dropdownElt = $(this.dropdownElt).offset({ left: $(this.expandButtonElt).offset().left });
} }
private onKeyPress(event: React.KeyboardEvent): boolean { private onKeyPress(event: React.KeyboardEvent): boolean {

View File

@@ -15,22 +15,7 @@ import {
ProgressIndicator, ProgressIndicator,
TextField, TextField,
} from "@fluentui/react"; } from "@fluentui/react";
import React, { FC } from "react"; import React, { FunctionComponent } from "react";
import create, { UseStore } from "zustand";
export interface DialogState {
visible: boolean;
dialogProps?: DialogProps;
openDialog: (props: DialogProps) => void;
closeDialog: () => void;
}
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 }), true),
}));
export interface TextFieldProps extends ITextFieldProps { export interface TextFieldProps extends ITextFieldProps {
label: string; label: string;
@@ -50,6 +35,7 @@ export interface DialogProps {
title: string; title: string;
subText: string; subText: string;
isModal: boolean; isModal: boolean;
visible: boolean;
choiceGroupProps?: IChoiceGroupProps; choiceGroupProps?: IChoiceGroupProps;
textFieldProps?: TextFieldProps; textFieldProps?: TextFieldProps;
linkProps?: LinkProps; linkProps?: LinkProps;
@@ -70,26 +56,24 @@ const DIALOG_TITLE_FONT_SIZE = "17px";
const DIALOG_TITLE_FONT_WEIGHT = 400; const DIALOG_TITLE_FONT_WEIGHT = 400;
const DIALOG_SUBTEXT_FONT_SIZE = "15px"; const DIALOG_SUBTEXT_FONT_SIZE = "15px";
export const Dialog: FC = () => { export const Dialog: FunctionComponent<DialogProps> = ({
const { visible, dialogProps: props } = useDialog(); title,
const { subText,
title, isModal,
subText, visible,
isModal, choiceGroupProps,
choiceGroupProps, textFieldProps,
textFieldProps, linkProps,
linkProps, progressIndicatorProps,
progressIndicatorProps, primaryButtonText,
primaryButtonText, secondaryButtonText,
secondaryButtonText, onPrimaryButtonClick,
onPrimaryButtonClick, onSecondaryButtonClick,
onSecondaryButtonClick, primaryButtonDisabled,
primaryButtonDisabled, type,
type, showCloseButton,
showCloseButton, onDismiss,
onDismiss, }: DialogProps) => {
} = props || {};
const dialogProps: IDialogProps = { const dialogProps: IDialogProps = {
hidden: !visible, hidden: !visible,
dialogContentProps: { dialogContentProps: {
@@ -121,7 +105,7 @@ export const Dialog: FC = () => {
} }
: {}; : {};
return visible ? ( return (
<FluentDialog {...dialogProps}> <FluentDialog {...dialogProps}>
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />} {choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
{textFieldProps && <TextField {...textFieldProps} />} {textFieldProps && <TextField {...textFieldProps} />}
@@ -136,7 +120,5 @@ export const Dialog: FC = () => {
{secondaryButtonProps && <DefaultButton {...secondaryButtonProps} />} {secondaryButtonProps && <DefaultButton {...secondaryButtonProps} />}
</DialogFooter> </DialogFooter>
</FluentDialog> </FluentDialog>
) : (
<></>
); );
}; };

View File

@@ -0,0 +1,105 @@
import React from "react";
import { shallow, mount } from "enzyme";
import { DefaultDirectoryDropdownComponent, DefaultDirectoryDropdownProps } from "./DefaultDirectoryDropdownComponent";
import { Tenant } from "../../../Contracts/DataModels";
const createBlankProps = (): DefaultDirectoryDropdownProps => {
return {
defaultDirectoryId: "",
directories: [],
onDefaultDirectoryChange: jest.fn(),
};
};
const createBlankDirectory = (): Tenant => {
return {
countryCode: "",
displayName: "",
domains: [],
id: "",
tenantId: "",
};
};
describe("test render", () => {
it("renders with no directories", () => {
const props = createBlankProps();
const wrapper = shallow(<DefaultDirectoryDropdownComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders with directories but no default", () => {
const props = createBlankProps();
const tenant1 = createBlankDirectory();
tenant1.displayName = "Microsoft";
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
const tenant2 = createBlankDirectory();
tenant1.displayName = "Macrohard";
tenant1.tenantId = "asdfghjklzxcvbnm9876543210";
props.directories = [tenant1, tenant2];
const wrapper = shallow(<DefaultDirectoryDropdownComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders with directories and default", () => {
const props = createBlankProps();
const tenant1 = createBlankDirectory();
tenant1.displayName = "Microsoft";
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
const tenant2 = createBlankDirectory();
tenant1.displayName = "Macrohard";
tenant1.tenantId = "asdfghjklzxcvbnm9876543210";
props.directories = [tenant1, tenant2];
props.defaultDirectoryId = "asdfghjklzxcvbnm9876543210";
const wrapper = shallow(<DefaultDirectoryDropdownComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders with directories and last visit default", () => {
const props = createBlankProps();
const tenant1 = createBlankDirectory();
tenant1.displayName = "Microsoft";
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
const tenant2 = createBlankDirectory();
tenant1.displayName = "Macrohard";
tenant1.tenantId = "asdfghjklzxcvbnm9876543210";
props.directories = [tenant1, tenant2];
props.defaultDirectoryId = "lastVisited";
const wrapper = shallow(<DefaultDirectoryDropdownComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
});
describe("test function", () => {
it("on default directory change", () => {
const props = createBlankProps();
const tenant1 = createBlankDirectory();
tenant1.displayName = "Microsoft";
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
const tenant2 = createBlankDirectory();
tenant1.displayName = "Macrohard";
tenant1.tenantId = "asdfghjklzxcvbnm9876543210";
props.directories = [tenant1, tenant2];
props.defaultDirectoryId = "lastVisited";
const wrapper = mount(<DefaultDirectoryDropdownComponent {...props} />);
wrapper.find("div.defaultDirectoryDropdown").find("div.ms-Dropdown").simulate("click");
expect(wrapper.exists("div.ms-Callout-main")).toBe(true);
wrapper.find("button.ms-Dropdown-item").at(1).simulate("click");
expect(props.onDefaultDirectoryChange).toBeCalled();
expect(props.onDefaultDirectoryChange).toHaveBeenCalled();
wrapper.find("div.defaultDirectoryDropdown").find("div.ms-Dropdown").simulate("click");
expect(wrapper.exists("div.ms-Callout-main")).toBe(true);
wrapper.find("button.ms-Dropdown-item").at(0).simulate("click");
expect(props.onDefaultDirectoryChange).toBeCalled();
expect(props.onDefaultDirectoryChange).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,71 @@
/**
* React component for Switch Directory
*/
import { Dropdown, IDropdownOption, IDropdownProps } from "@fluentui/react";
import * as React from "react";
import _ from "underscore";
import { Tenant } from "../../../Contracts/DataModels";
export interface DefaultDirectoryDropdownProps {
directories: Array<Tenant>;
defaultDirectoryId: string;
onDefaultDirectoryChange: (newDirectory: Tenant) => void;
}
export class DefaultDirectoryDropdownComponent extends React.Component<DefaultDirectoryDropdownProps> {
public static readonly lastVisitedKey: string = "lastVisited";
public render(): JSX.Element {
const lastVisitedOption: IDropdownOption = {
key: DefaultDirectoryDropdownComponent.lastVisitedKey,
text: "Sign in to your last visited directory",
};
const directoryOptions: Array<IDropdownOption> = this.props.directories.map(
(dirc): IDropdownOption => {
return {
key: dirc.tenantId,
text: `${dirc.displayName}(${dirc.tenantId})`,
};
}
);
const dropDownOptions: Array<IDropdownOption> = [lastVisitedOption, ...directoryOptions];
const dropDownProps: IDropdownProps = {
label: "Set your default directory",
options: dropDownOptions,
defaultSelectedKey: this.props.defaultDirectoryId ? this.props.defaultDirectoryId : lastVisitedOption.key,
onChange: this._onDropdownChange,
className: "defaultDirectoryDropdown",
};
return <Dropdown {...dropDownProps} />;
}
private _onDropdownChange = (e: React.FormEvent<HTMLDivElement>, option?: IDropdownOption, index?: number): void => {
if (!option || !option.key) {
return;
}
if (option.key === this.props.defaultDirectoryId) {
return;
}
if (option.key === DefaultDirectoryDropdownComponent.lastVisitedKey) {
this.props.onDefaultDirectoryChange({
tenantId: option.key,
countryCode: undefined,
displayName: undefined,
domains: [],
id: undefined,
});
return;
}
const selectedDirectory = _.find(this.props.directories, (d) => d.tenantId === option.key);
if (!selectedDirectory) {
return;
}
this.props.onDefaultDirectoryChange(selectedDirectory);
};
}

View File

@@ -0,0 +1,36 @@
import * as ko from "knockout";
import * as React from "react";
import { DirectoryListComponent, DirectoryListProps } from "./DirectoryListComponent";
import { DefaultDirectoryDropdownComponent, DefaultDirectoryDropdownProps } from "./DefaultDirectoryDropdownComponent";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
export class DirectoryComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
constructor(
private _dropdownProps: ko.Observable<DefaultDirectoryDropdownProps>,
private _listProps: ko.Observable<DirectoryListProps>
) {
this._dropdownProps.subscribe(() => this.forceRender());
this._listProps.subscribe(() => this.forceRender());
this.parameters = ko.observable<number>(Date.now());
}
public renderComponent(): JSX.Element {
return (
<div>
<div className="directoryDropdownContainer">
<DefaultDirectoryDropdownComponent {...this._dropdownProps()} />
</div>
<div className="directoryDivider" />
<div className="directoryListContainer">
<DirectoryListComponent {...this._listProps()} />
</div>
</div>
);
}
public forceRender(): void {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
}

View File

@@ -0,0 +1,78 @@
import React from "react";
import { shallow, mount } from "enzyme";
import { DirectoryListComponent, DirectoryListProps } from "./DirectoryListComponent";
import { Tenant } from "../../../Contracts/DataModels";
const createBlankProps = (): DirectoryListProps => {
return {
selectedDirectoryId: undefined,
directories: [],
onNewDirectorySelected: jest.fn(),
};
};
const createBlankDirectory = (): Tenant => {
return {
countryCode: undefined,
displayName: undefined,
domains: [],
id: undefined,
tenantId: undefined,
};
};
describe("test render", () => {
it("renders with no directories", () => {
const props = createBlankProps();
const wrapper = shallow(<DirectoryListComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders with directories and selected", () => {
const props = createBlankProps();
const tenant1 = createBlankDirectory();
tenant1.displayName = "Microsoft";
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
const tenant2 = createBlankDirectory();
tenant1.displayName = "Macrohard";
tenant1.tenantId = "asdfghjklzxcvbnm9876543210";
props.directories = [tenant1, tenant2];
props.selectedDirectoryId = "asdfghjklzxcvbnm9876543210";
const wrapper = shallow(<DirectoryListComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders with filters", () => {
const props = createBlankProps();
const tenant1 = createBlankDirectory();
tenant1.displayName = "Microsoft";
tenant1.tenantId = "1234567890";
const tenant2 = createBlankDirectory();
tenant1.displayName = "Macrohard";
tenant1.tenantId = "9876543210";
props.directories = [tenant1, tenant2];
props.selectedDirectoryId = "9876543210";
const wrapper = mount(<DirectoryListComponent {...props} />);
wrapper.find("input.ms-TextField-field").simulate("change", { target: { value: "Macro" } });
expect(wrapper).toMatchSnapshot();
});
});
describe("test function", () => {
it("on new directory selected", () => {
const props = createBlankProps();
const tenant1 = createBlankDirectory();
tenant1.displayName = "Microsoft";
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
props.directories = [tenant1];
const wrapper = mount(<DirectoryListComponent {...props} />);
wrapper.find("button.directoryListButton").simulate("click");
expect(props.onNewDirectorySelected).toBeCalled();
expect(props.onNewDirectorySelected).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,126 @@
import {
DefaultButton,
IButtonProps,
ITextFieldProps,
List,
ScrollablePane,
Sticky,
StickyPositionType,
TextField,
} from "@fluentui/react";
import * as React from "react";
import _ from "underscore";
import { Tenant } from "../../../Contracts/DataModels";
export interface DirectoryListProps {
directories: Array<Tenant>;
selectedDirectoryId: string;
onNewDirectorySelected: (newDirectory: Tenant) => void;
}
export interface DirectoryListComponentState {
filterText: string;
}
// onRenderCell is not called when selectedDirectoryId changed, so add a selected state to force render
interface ListTenant extends Tenant {
selected?: boolean;
}
export class DirectoryListComponent extends React.Component<DirectoryListProps, DirectoryListComponentState> {
constructor(props: DirectoryListProps) {
super(props);
this.state = {
filterText: "",
};
}
public render(): JSX.Element {
const { directories: originalItems, selectedDirectoryId } = this.props;
const { filterText } = this.state;
const filteredItems =
originalItems && originalItems.length && filterText
? originalItems.filter(
(directory) =>
directory.displayName &&
directory.displayName.toLowerCase().indexOf(filterText && filterText.toLowerCase()) >= 0
)
: originalItems;
const filteredItemsSelected = filteredItems.map((t) => {
let tenant: ListTenant = t;
tenant.selected = t.tenantId === selectedDirectoryId;
return tenant;
});
const textFieldProps: ITextFieldProps = {
className: "directoryListFilterTextBox",
placeholder: "Filter by directory name",
onChange: this._onFilterChanged,
ariaLabel: "Directory filter text box",
};
// TODO: add magnify glass to search bar with onRenderSuffix
return (
<ScrollablePane data-is-scrollable="true">
<Sticky stickyPosition={StickyPositionType.Header}>
<TextField {...textFieldProps} />
</Sticky>
<List items={filteredItemsSelected} onRenderCell={this._onRenderCell} />
</ScrollablePane>
);
}
private _onFilterChanged = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, text?: string): void => {
this.setState({
filterText: text,
});
};
private _onRenderCell = (directory: ListTenant): JSX.Element => {
const buttonProps: IButtonProps = {
disabled: directory.selected || false,
className: "directoryListButton",
onClick: this._onNewDirectoryClick,
styles: {
root: {
backgroundColor: "transparent",
height: "auto",
borderBottom: "1px solid #ccc",
padding: "1px 0",
width: "100%",
},
rootDisabled: {
backgroundColor: "#f1f1f8",
},
rootHovered: {
backgroundColor: "rgba(85,179,255,.1)",
},
flexContainer: {
height: "auto",
justifyContent: "flex-start",
},
},
};
return (
<DefaultButton {...buttonProps}>
<div className="directoryListItem" data-is-focusable={true}>
<div className="directoryListItemName">{directory.displayName}</div>
<div className="directoryListItemId">{directory.tenantId}</div>
</div>
</DefaultButton>
);
};
private _onNewDirectoryClick = (e: React.MouseEvent<HTMLButtonElement>): void => {
if (!e || !e.currentTarget) {
return;
}
const buttonElement = e.currentTarget;
const selectedDirectoryId = buttonElement.getElementsByClassName("directoryListItemId")[0].textContent;
const selectedDirectory = _.find(this.props.directories, (d) => d.tenantId === selectedDirectoryId);
this.props.onNewDirectorySelected(selectedDirectory);
};
}

View File

@@ -0,0 +1,93 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`test render renders with directories and default 1`] = `
<Dropdown
className="defaultDirectoryDropdown"
defaultSelectedKey="asdfghjklzxcvbnm9876543210"
label="Set your default directory"
onChange={[Function]}
options={
Array [
Object {
"key": "lastVisited",
"text": "Sign in to your last visited directory",
},
Object {
"key": "asdfghjklzxcvbnm9876543210",
"text": "Macrohard(asdfghjklzxcvbnm9876543210)",
},
Object {
"key": "",
"text": "()",
},
]
}
/>
`;
exports[`test render renders with directories and last visit default 1`] = `
<Dropdown
className="defaultDirectoryDropdown"
defaultSelectedKey="lastVisited"
label="Set your default directory"
onChange={[Function]}
options={
Array [
Object {
"key": "lastVisited",
"text": "Sign in to your last visited directory",
},
Object {
"key": "asdfghjklzxcvbnm9876543210",
"text": "Macrohard(asdfghjklzxcvbnm9876543210)",
},
Object {
"key": "",
"text": "()",
},
]
}
/>
`;
exports[`test render renders with directories but no default 1`] = `
<Dropdown
className="defaultDirectoryDropdown"
defaultSelectedKey="lastVisited"
label="Set your default directory"
onChange={[Function]}
options={
Array [
Object {
"key": "lastVisited",
"text": "Sign in to your last visited directory",
},
Object {
"key": "asdfghjklzxcvbnm9876543210",
"text": "Macrohard(asdfghjklzxcvbnm9876543210)",
},
Object {
"key": "",
"text": "()",
},
]
}
/>
`;
exports[`test render renders with no directories 1`] = `
<Dropdown
className="defaultDirectoryDropdown"
defaultSelectedKey="lastVisited"
label="Set your default directory"
onChange={[Function]}
options={
Array [
Object {
"key": "lastVisited",
"text": "Sign in to your last visited directory",
},
]
}
/>
`;

View File

@@ -0,0 +1,64 @@
import * as ko from "knockout";
import { DynamicListComponent, DynamicListParams, DynamicListItem } from "./DynamicListComponent";
const $ = (selector: string) => document.querySelector(selector) as HTMLElement;
function buildComponent(buttonOptions: any) {
document.body.innerHTML = DynamicListComponent.template as any;
const vm = new DynamicListComponent.viewModel(buttonOptions);
ko.applyBindings(vm);
}
describe("Dynamic List Component", () => {
const mockPlaceHolder = "Write here";
const mockButton = "Add something";
const mockValue = "/someText";
const mockAriaLabel = "Add ariaLabel";
const items: ko.ObservableArray<DynamicListItem> = ko.observableArray<DynamicListItem>();
function buildListOptions(
items: ko.ObservableArray<DynamicListItem>,
placeholder?: string,
mockButton?: string
): DynamicListParams {
return {
placeholder: placeholder,
listItems: items,
buttonText: mockButton,
ariaLabel: mockAriaLabel,
};
}
afterEach(() => {
ko.cleanNode(document);
});
describe("Rendering", () => {
it("should display button text", () => {
const params = buildListOptions(items, mockPlaceHolder, mockButton);
buildComponent(params);
expect($(".dynamicListItemAdd").textContent).toContain(mockButton);
});
});
describe("Behavior", () => {
it("should add items to the list", () => {
const params = buildListOptions(items, mockPlaceHolder, mockButton);
buildComponent(params);
$(".dynamicListItemAdd").click();
expect(items().length).toBe(1);
const input = document.getElementsByClassName("dynamicListItem").item(0).children[0];
input.setAttribute("value", mockValue);
input.dispatchEvent(new Event("change"));
input.dispatchEvent(new Event("blur"));
expect(items()[0].value()).toBe(mockValue);
});
it("should remove items from the list", () => {
const params = buildListOptions(items, mockPlaceHolder);
buildComponent(params);
$(".dynamicListItemDelete").click();
expect(items().length).toBe(0);
});
});
});

View File

@@ -0,0 +1,59 @@
@import "../../../../less/Common/Constants";
.dynamicList {
width: 100%;
.dynamicListContainer {
.dynamicListItem {
justify-content: space-around;
margin-bottom: @MediumSpace;
input {
width: @newCollectionPaneInputWidth;
margin: auto;
font-size: @mediumFontSize;
padding: @SmallSpace @DefaultSpace;
color: @BaseDark;
}
.dynamicListItemDelete {
padding: @SmallSpace @SmallSpace @DefaultSpace;
margin-left: @SmallSpace;
&:hover {
.hover();
}
&:active {
.active();
}
img {
.dataExplorerIcons();
}
}
}
}
.dynamicListItemNew {
margin-top: @LargeSpace;
.dynamicListItemAdd {
padding: @DefaultSpace;
cursor: pointer;
&:hover {
.hover();
}
&:active {
.active();
}
img {
.dataExplorerIcons();
margin: 0px @SmallSpace @SmallSpace 0px;
}
}
}
}

View File

@@ -0,0 +1,117 @@
/**
* Dynamic list:
*
* Creates a list of dynamic inputs that can be populated and deleted.
*
* How to use in your markup:
* <dynamic-list params="{ listItems: anObservableArrayOfDynamicListItem, placeholder: 'Text to display in placeholder', ariaLabel: 'Text for aria-label', buttonText: 'Add item' }">
* </dynamic-list>
*
*/
import * as ko from "knockout";
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
import { KeyCodes } from "../../../Common/Constants";
import template from "./dynamic-list.html";
/**
* Parameters for this component
*/
export interface DynamicListParams {
/**
* Observable list of items to update
*/
listItems: ko.ObservableArray<DynamicListItem>;
/**
* Placeholder text to use on inputs
*/
placeholder?: string;
/**
* Text to use as aria-label
*/
ariaLabel: string;
/**
* Text for the button to add items
*/
buttonText?: string;
/**
* Callback triggered when the template is bound to the component (for testing purposes)
*/
onTemplateReady?: () => void;
}
/**
* Item in the dynamic list
*/
export interface DynamicListItem {
value: ko.Observable<string>;
}
export class DynamicListViewModel extends WaitsForTemplateViewModel {
public placeholder: string;
public ariaLabel: string;
public buttonText: string;
public newItem: ko.Observable<string>;
public isTemplateReady: ko.Observable<boolean>;
public listItems: ko.ObservableArray<DynamicListItem>;
public constructor(options: DynamicListParams) {
super();
super.onTemplateReady((isTemplateReady: boolean) => {
if (isTemplateReady && options.onTemplateReady) {
options.onTemplateReady();
}
});
const params: DynamicListParams = options;
const paramsPlaceholder: string = params.placeholder;
const paramsButtonText: string = params.buttonText;
this.placeholder = paramsPlaceholder || "Write a value";
this.ariaLabel = "Unique keys";
this.buttonText = paramsButtonText || "Add item";
this.listItems = params.listItems || ko.observableArray<DynamicListItem>();
this.newItem = ko.observable("");
}
public removeItem = (data: any, event: MouseEvent | KeyboardEvent): void => {
const context = ko.contextFor(event.target as Node);
this.listItems.splice(context.$index(), 1);
document.getElementById("addUniqueKeyBtn").focus();
};
public onRemoveItemKeyPress = (data: any, event: KeyboardEvent, source: any): boolean => {
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
this.removeItem(data, event);
(document.querySelector(".dynamicListItem:last-of-type input") as HTMLElement).focus();
event.stopPropagation();
return false;
}
return true;
};
public addItem(): void {
this.listItems.push({ value: ko.observable("") });
(document.querySelector(".dynamicListItem:last-of-type input") as HTMLElement).focus();
}
public onAddItemKeyPress = (source: any, event: KeyboardEvent): boolean => {
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
this.addItem();
event.stopPropagation();
return false;
}
return true;
};
}
/**
* Helper class for ko component registration
*/
export const DynamicListComponent = {
viewModel: DynamicListViewModel,
template,
};

View File

@@ -0,0 +1,34 @@
<div class="dynamicList" data-bind="setTemplateReady: true">
<div class="dynamicListContainer" data-bind="foreach: listItems">
<div class="dynamicListItem">
<input
id="uniqueKeyItems"
type="text"
autocomplete="off"
data-bind="value: value, attr: {placeholder: $parent.placeholder, 'aria-label': $parent.ariaLabel}"
/>
<span
class="dynamicListItemDelete"
title="Remove item"
role="button"
aria-label="Remove item"
tabindex="0"
data-bind="click: $parent.removeItem, event: { keydown: $parent.onRemoveItemKeyPress }"
>
<img src="/delete.svg" alt="Remove item" />
</span>
</div>
</div>
<div class="dynamicListItemNew">
<span
class="dynamicListItemAdd"
id="addUniqueKeyBtn"
role="button"
aria-label="Add unique key"
tabindex="0"
data-bind="click: addItem, event: { keydown: onAddItemKeyPress }"
>
<img src="/Add-property.svg" data-bind="attr: {alt: buttonText}" /> <span data-bind="text: buttonText"></span>
</span>
</div>
</div>

View File

@@ -1,11 +1,6 @@
import { Spinner, SpinnerSize } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { loadMonaco, monaco } from "../../LazyMonaco"; import { loadMonaco, monaco } from "../../LazyMonaco";
// import "./EditorReact.less";
interface EditorReactStates {
showEditor: boolean;
}
export interface EditorReactProps { export interface EditorReactProps {
language: string; language: string;
content: string; content: string;
@@ -17,26 +12,22 @@ export interface EditorReactProps {
theme?: string; // Monaco editor theme theme?: string; // Monaco editor theme
} }
export class EditorReact extends React.Component<EditorReactProps, EditorReactStates> { export class EditorReact extends React.Component<EditorReactProps> {
private rootNode: HTMLElement; private rootNode: HTMLElement;
private editor: monaco.editor.IStandaloneCodeEditor; private editor: monaco.editor.IStandaloneCodeEditor;
private selectionListener: monaco.IDisposable; private selectionListener: monaco.IDisposable;
public constructor(props: EditorReactProps) { public constructor(props: EditorReactProps) {
super(props); super(props);
this.state = {
showEditor: false,
};
} }
public componentDidMount(): void { public componentDidMount(): void {
this.createEditor(this.configureEditor.bind(this)); this.createEditor(this.configureEditor.bind(this));
} }
public componentDidUpdate(previous: EditorReactProps) { public shouldComponentUpdate(): boolean {
if (this.props.content !== previous.content) { // Prevents component re-rendering
this.editor.setValue(this.props.content); return false;
}
} }
public componentWillUnmount(): void { public componentWillUnmount(): void {
@@ -44,19 +35,14 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
} }
public render(): JSX.Element { public render(): JSX.Element {
return ( return <div className="jsonEditor" ref={(elt: HTMLElement) => this.setRef(elt)} />;
<React.Fragment>
{!this.state.showEditor && <Spinner size={SpinnerSize.large} className="spinner" />}
<div className="jsonEditor" ref={(elt: HTMLElement) => this.setRef(elt)} />
</React.Fragment>
);
} }
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) { protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
this.editor = editor; this.editor = editor;
const queryEditorModel = this.editor.getModel(); const queryEditorModel = this.editor.getModel();
if (!this.props.isReadOnly && this.props.onContentChanged) { if (!this.props.isReadOnly && this.props.onContentChanged) {
queryEditorModel.onDidChangeContent(() => { queryEditorModel.onDidChangeContent((e: monaco.editor.IModelContentChangedEvent) => {
const queryEditorModel = this.editor.getModel(); const queryEditorModel = this.editor.getModel();
this.props.onContentChanged(queryEditorModel.getValue()); this.props.onContentChanged(queryEditorModel.getValue());
}); });
@@ -90,12 +76,6 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
this.rootNode.innerHTML = ""; this.rootNode.innerHTML = "";
const monaco = await loadMonaco(); const monaco = await loadMonaco();
createCallback(monaco.editor.create(this.rootNode, options)); createCallback(monaco.editor.create(this.rootNode, options));
if (this.rootNode.innerHTML) {
this.setState({
showEditor: true,
});
}
} }
private setRef(element: HTMLElement): void { private setRef(element: HTMLElement): void {

View File

@@ -56,7 +56,7 @@ export class GitHubReposComponent extends React.Component<GitHubReposComponentPr
return ( return (
<> <>
<div>{content}</div> <div className={"paneMainContent"}>{content}</div>
{!this.props.showAuthorizeAccess && ( {!this.props.showAuthorizeAccess && (
<> <>
<div className={"paneFooter"} style={ContentFooterStyle}> <div className={"paneFooter"} style={ContentFooterStyle}>

View File

@@ -0,0 +1,20 @@
import ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { GitHubReposComponent, GitHubReposComponentProps } from "./GitHubReposComponent";
export class GitHubReposComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
constructor(private props: GitHubReposComponentProps) {
this.parameters = ko.observable<number>(Date.now());
}
public renderComponent(): JSX.Element {
return <GitHubReposComponent {...this.props} />;
}
public triggerRender(): void {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
}

View File

@@ -1,90 +1,154 @@
import { shallow } from "enzyme";
import React from "react";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import { NotebookTerminalComponent, NotebookTerminalComponentProps } from "./NotebookTerminalComponent"; import { NotebookTerminalComponent } from "./NotebookTerminalComponent";
const testAccount: DataModels.DatabaseAccount = { const createTestDatabaseAccount = (): DataModels.DatabaseAccount => {
id: "id", return {
kind: "kind", id: "testId",
location: "location", kind: "testKind",
name: "name", location: "testLocation",
properties: { name: "testName",
documentEndpoint: "https://testDocumentEndpoint.azure.com/", properties: {
}, cassandraEndpoint: null,
type: "type", documentEndpoint: "https://testDocumentEndpoint.azure.com/",
gremlinEndpoint: null,
tableEndpoint: null,
},
type: "testType",
};
}; };
const testMongo32Account: DataModels.DatabaseAccount = { const createTestMongo32DatabaseAccount = (): DataModels.DatabaseAccount => {
...testAccount, 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 = { const createTestMongo36DatabaseAccount = (): DataModels.DatabaseAccount => {
...testAccount, return {
properties: { id: "testId",
mongoEndpoint: "https://testMongoEndpoint.azure.com/", 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 = { const createTestCassandraDatabaseAccount = (): DataModels.DatabaseAccount => {
...testAccount, return {
properties: { id: "testId",
cassandraEndpoint: "https://testCassandraEndpoint.azure.com/", kind: "testKind",
}, location: "testLocation",
name: "testName",
properties: {
cassandraEndpoint: "https://testCassandraEndpoint.azure.com/",
documentEndpoint: null,
gremlinEndpoint: null,
tableEndpoint: null,
},
type: "testType",
};
}; };
const testNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = { const createTerminal = (): NotebookTerminalComponent => {
authToken: "authToken", return new NotebookTerminalComponent({
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com", notebookServerInfo: {
authToken: "testAuthToken",
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/",
},
databaseAccount: createTestDatabaseAccount(),
});
}; };
const testMongoNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = { const createMongo32Terminal = (): NotebookTerminalComponent => {
authToken: "authToken", return new NotebookTerminalComponent({
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo", notebookServerInfo: {
authToken: "testAuthToken",
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
},
databaseAccount: createTestMongo32DatabaseAccount(),
});
}; };
const testCassandraNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = { const createMongo36Terminal = (): NotebookTerminalComponent => {
authToken: "authToken", return new NotebookTerminalComponent({
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/cassandra", 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", () => { describe("NotebookTerminalComponent", () => {
it("renders terminal", () => { it("getTerminalParams: Test for terminal", () => {
const props: NotebookTerminalComponentProps = { const terminal: NotebookTerminalComponent = createTerminal();
databaseAccount: testAccount, const params: Map<string, string> = terminal.getTerminalParams();
notebookServerInfo: testNotebookServerInfo,
};
const wrapper = shallow(<NotebookTerminalComponent {...props} />); expect(params).toEqual(
expect(wrapper).toMatchSnapshot(); new Map<string, string>([["terminal", "true"]])
);
}); });
it("renders mongo 3.2 shell", () => { it("getTerminalParams: Test for Mongo 3.2 terminal", () => {
const props: NotebookTerminalComponentProps = { const terminal: NotebookTerminalComponent = createMongo32Terminal();
databaseAccount: testMongo32Account, const params: Map<string, string> = terminal.getTerminalParams();
notebookServerInfo: testMongoNotebookServerInfo,
};
const wrapper = shallow(<NotebookTerminalComponent {...props} />); expect(params).toEqual(
expect(wrapper).toMatchSnapshot(); new Map<string, string>([
["terminal", "true"],
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.documentEndpoint).host],
])
);
}); });
it("renders mongo 3.6 shell", () => { it("getTerminalParams: Test for Mongo 3.6 terminal", () => {
const props: NotebookTerminalComponentProps = { const terminal: NotebookTerminalComponent = createMongo36Terminal();
databaseAccount: testMongo36Account, const params: Map<string, string> = terminal.getTerminalParams();
notebookServerInfo: testMongoNotebookServerInfo,
};
const wrapper = shallow(<NotebookTerminalComponent {...props} />); expect(params).toEqual(
expect(wrapper).toMatchSnapshot(); new Map<string, string>([
["terminal", "true"],
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.mongoEndpoint).host],
])
);
}); });
it("renders cassandra shell", () => { it("getTerminalParams: Test for Cassandra terminal", () => {
const props: NotebookTerminalComponentProps = { const terminal: NotebookTerminalComponent = createCassandraTerminal();
databaseAccount: testCassandraAccount, const params: Map<string, string> = terminal.getTerminalParams();
notebookServerInfo: testCassandraNotebookServerInfo,
};
const wrapper = shallow(<NotebookTerminalComponent {...props} />); expect(params).toEqual(
expect(wrapper).toMatchSnapshot(); new Map<string, string>([
["terminal", "true"],
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.cassandraEndpoint).host],
])
);
}); });
}); });

View File

@@ -2,12 +2,12 @@
* Wrapper around Notebook server terminal * Wrapper around Notebook server terminal
*/ */
import postRobot from "post-robot";
import * as React from "react"; import * as React from "react";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import { TerminalProps } from "../../../Terminal/TerminalProps";
import { userContext } from "../../../UserContext";
import * as StringUtils from "../../../Utils/StringUtils"; import * as StringUtils from "../../../Utils/StringUtils";
import { userContext } from "../../../UserContext";
import { TerminalQueryParams } from "../../../Common/Constants";
import { handleError } from "../../../Common/ErrorHandlingUtils";
export interface NotebookTerminalComponentProps { export interface NotebookTerminalComponentProps {
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo; notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
@@ -15,69 +15,79 @@ export interface NotebookTerminalComponentProps {
} }
export class NotebookTerminalComponent extends React.Component<NotebookTerminalComponentProps> { export class NotebookTerminalComponent extends React.Component<NotebookTerminalComponentProps> {
private terminalWindow: Window;
constructor(props: NotebookTerminalComponentProps) { constructor(props: NotebookTerminalComponentProps) {
super(props); super(props);
} }
componentDidMount(): void {
this.sendPropsToTerminalFrame();
}
public render(): JSX.Element { public render(): JSX.Element {
return ( return (
<div className="notebookTerminalContainer"> <div className="notebookTerminalContainer">
<iframe <iframe
title="Terminal to Notebook Server" title="Terminal to Notebook Server"
onLoad={(event) => this.handleFrameLoad(event)} src={NotebookTerminalComponent.createNotebookAppSrc(this.props.notebookServerInfo, this.getTerminalParams())}
src="terminal.html"
/> />
</div> </div>
); );
} }
handleFrameLoad(event: React.SyntheticEvent<HTMLIFrameElement, Event>): void { public getTerminalParams(): Map<string, string> {
this.terminalWindow = (event.target as HTMLIFrameElement).contentWindow; let params: Map<string, string> = new Map<string, string>();
this.sendPropsToTerminalFrame(); params.set(TerminalQueryParams.Terminal, "true");
}
sendPropsToTerminalFrame(): void { const terminalEndpoint: string = this.tryGetTerminalEndpoint();
if (!this.terminalWindow) { if (terminalEndpoint) {
return; params.set(TerminalQueryParams.TerminalEndpoint, terminalEndpoint);
} }
const props: TerminalProps = { return params;
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,
});
} }
public tryGetTerminalEndpoint(): string | undefined { public tryGetTerminalEndpoint(): string | null {
let terminalEndpoint: string | undefined; let terminalEndpoint: string | null;
const notebookServerEndpoint = this.props.notebookServerInfo?.notebookServerEndpoint; const notebookServerEndpoint: string = this.props.notebookServerInfo.notebookServerEndpoint;
if (StringUtils.endsWith(notebookServerEndpoint, "mongo")) { if (StringUtils.endsWith(notebookServerEndpoint, "mongo")) {
// mongoEndpoint is only available for Mongo 3.6 and higher, fallback to documentEndpoint otherwise let mongoShellEndpoint: string = this.props.databaseAccount.properties.mongoEndpoint;
terminalEndpoint = if (!mongoShellEndpoint) {
this.props.databaseAccount?.properties.mongoEndpoint || this.props.databaseAccount?.properties.documentEndpoint; // 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")) { } else if (StringUtils.endsWith(notebookServerEndpoint, "cassandra")) {
terminalEndpoint = this.props.databaseAccount?.properties.cassandraEndpoint; terminalEndpoint = this.props.databaseAccount.properties.cassandraEndpoint;
} }
if (terminalEndpoint) { if (terminalEndpoint) {
return new URL(terminalEndpoint).host; return new URL(terminalEndpoint).host;
} }
return null;
}
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;
} }
} }

View File

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

View File

@@ -28,6 +28,7 @@ import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryCons
import { trace } from "../../../Shared/Telemetry/TelemetryProcessor"; import { trace } from "../../../Shared/Telemetry/TelemetryProcessor";
import * as GalleryUtils from "../../../Utils/GalleryUtils"; import * as GalleryUtils from "../../../Utils/GalleryUtils";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { Dialog, DialogProps } from "../Dialog";
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent"; import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
import { CodeOfConduct } from "./CodeOfConduct/CodeOfConduct"; import { CodeOfConduct } from "./CodeOfConduct/CodeOfConduct";
import "./GalleryViewerComponent.less"; import "./GalleryViewerComponent.less";
@@ -67,6 +68,7 @@ interface GalleryViewerComponentState {
selectedTab: GalleryTab; selectedTab: GalleryTab;
sortBy: SortBy; sortBy: SortBy;
searchText: string; searchText: string;
dialogProps: DialogProps;
isCodeOfConductAccepted: boolean; isCodeOfConductAccepted: boolean;
isFetchingPublishedNotebooks: boolean; isFetchingPublishedNotebooks: boolean;
isFetchingFavouriteNotebooks: boolean; isFetchingFavouriteNotebooks: boolean;
@@ -117,6 +119,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
selectedTab: props.selectedTab, selectedTab: props.selectedTab,
sortBy: props.sortBy, sortBy: props.sortBy,
searchText: props.searchText, searchText: props.searchText,
dialogProps: undefined,
isCodeOfConductAccepted: undefined, isCodeOfConductAccepted: undefined,
isFetchingFavouriteNotebooks: true, isFetchingFavouriteNotebooks: true,
isFetchingPublishedNotebooks: true, isFetchingPublishedNotebooks: true,
@@ -184,6 +187,8 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
return ( return (
<div className="galleryContainer"> <div className="galleryContainer">
<Pivot {...pivotProps}>{pivotItems}</Pivot> <Pivot {...pivotProps}>{pivotItems}</Pivot>
{this.state.dialogProps && <Dialog {...this.state.dialogProps} />}
</div> </div>
); );
} }

View File

@@ -1,25 +1,25 @@
/** /**
* Wrapper around Notebook Viewer Read only content * Wrapper around Notebook Viewer Read only content
*/ */
import { IChoiceGroupProps, Icon, IProgressIndicatorProps, Link, ProgressIndicator } from "@fluentui/react";
import { Notebook } from "@nteract/commutable"; import { Notebook } from "@nteract/commutable";
import { createContentRef } from "@nteract/core"; import { createContentRef } from "@nteract/core";
import { IChoiceGroupProps, Icon, IProgressIndicatorProps, Link, ProgressIndicator } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { contents } from "rx-jupyter"; import { contents } from "rx-jupyter";
import { getErrorMessage, getErrorStack, handleError } from "../../../Common/ErrorHandlingUtils";
import { IGalleryItem, JunoClient } from "../../../Juno/JunoClient"; import { IGalleryItem, JunoClient } from "../../../Juno/JunoClient";
import { SessionStorageUtility } from "../../../Shared/StorageUtility";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
import * as GalleryUtils from "../../../Utils/GalleryUtils"; import * as GalleryUtils from "../../../Utils/GalleryUtils";
import { DialogHost } from "../../../Utils/GalleryUtils";
import Explorer from "../../Explorer";
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2"; import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper"; import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer"; import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
import { Dialog, TextFieldProps, useDialog } from "../Dialog"; import { Dialog, DialogProps, TextFieldProps } from "../Dialog";
import { NotebookMetadataComponent } from "./NotebookMetadataComponent"; import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
import "./NotebookViewerComponent.less"; import "./NotebookViewerComponent.less";
import Explorer from "../../Explorer";
import { SessionStorageUtility } from "../../../Shared/StorageUtility";
import { DialogHost } from "../../../Utils/GalleryUtils";
import { getErrorMessage, getErrorStack, handleError } from "../../../Common/ErrorHandlingUtils";
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
export interface NotebookViewerComponentProps { export interface NotebookViewerComponentProps {
container?: Explorer; container?: Explorer;
@@ -38,6 +38,7 @@ interface NotebookViewerComponentState {
content: Notebook; content: Notebook;
galleryItem?: IGalleryItem; galleryItem?: IGalleryItem;
isFavorite?: boolean; isFavorite?: boolean;
dialogProps: DialogProps;
showProgressBar: boolean; showProgressBar: boolean;
} }
@@ -55,7 +56,7 @@ export class NotebookViewerComponent
databaseAccountName: undefined, databaseAccountName: undefined,
defaultExperience: "NotebookViewer", defaultExperience: "NotebookViewer",
isReadOnly: true, isReadOnly: true,
cellEditorType: "codemirror", cellEditorType: "monaco",
autoSaveInterval: 365 * 24 * 3600 * 1000, // There is no way to turn off auto-save, set to 1 year autoSaveInterval: 365 * 24 * 3600 * 1000, // There is no way to turn off auto-save, set to 1 year
contentProvider: contents.JupyterContentProvider, // NotebookViewer only knows how to talk to Jupyter contents API contentProvider: contents.JupyterContentProvider, // NotebookViewer only knows how to talk to Jupyter contents API
}); });
@@ -69,6 +70,7 @@ export class NotebookViewerComponent
content: undefined, content: undefined,
galleryItem: props.galleryItem, galleryItem: props.galleryItem,
isFavorite: props.isFavorite, isFavorite: props.isFavorite,
dialogProps: undefined,
showProgressBar: true, showProgressBar: true,
}; };
@@ -164,7 +166,8 @@ export class NotebookViewerComponent
hideInputs: this.props.hideInputs, hideInputs: this.props.hideInputs,
hidePrompts: this.props.hidePrompts, hidePrompts: this.props.hidePrompts,
})} })}
<Dialog />
{this.state.dialogProps && <Dialog {...this.state.dialogProps} />}
</div> </div>
); );
} }
@@ -190,6 +193,7 @@ export class NotebookViewerComponent
}; };
} }
// DialogHost
showOkModalDialog( showOkModalDialog(
title: string, title: string,
msg: string, msg: string,
@@ -197,21 +201,25 @@ export class NotebookViewerComponent
onOk: () => void, onOk: () => void,
progressIndicatorProps?: IProgressIndicatorProps progressIndicatorProps?: IProgressIndicatorProps
): void { ): void {
useDialog.getState().openDialog({ this.setState({
isModal: true, dialogProps: {
title, isModal: true,
subText: msg, visible: true,
primaryButtonText: okLabel, title,
onPrimaryButtonClick: () => { subText: msg,
useDialog.getState().closeDialog(); primaryButtonText: okLabel,
onOk && onOk(); onPrimaryButtonClick: () => {
this.setState({ dialogProps: undefined });
onOk && onOk();
},
secondaryButtonText: undefined,
onSecondaryButtonClick: undefined,
progressIndicatorProps,
}, },
secondaryButtonText: undefined,
onSecondaryButtonClick: undefined,
progressIndicatorProps,
}); });
} }
// DialogHost
showOkCancelModalDialog( showOkCancelModalDialog(
title: string, title: string,
msg: string, msg: string,
@@ -224,24 +232,27 @@ export class NotebookViewerComponent
textFieldProps?: TextFieldProps, textFieldProps?: TextFieldProps,
primaryButtonDisabled?: boolean primaryButtonDisabled?: boolean
): void { ): void {
useDialog.getState().openDialog({ this.setState({
isModal: true, dialogProps: {
title, isModal: true,
subText: msg, visible: true,
primaryButtonText: okLabel, title,
secondaryButtonText: cancelLabel, subText: msg,
onPrimaryButtonClick: () => { primaryButtonText: okLabel,
useDialog.getState().closeDialog(); secondaryButtonText: cancelLabel,
onOk && onOk(); onPrimaryButtonClick: () => {
this.setState({ dialogProps: undefined });
onOk && onOk();
},
onSecondaryButtonClick: () => {
this.setState({ dialogProps: undefined });
onCancel && onCancel();
},
progressIndicatorProps,
choiceGroupProps,
textFieldProps,
primaryButtonDisabled,
}, },
onSecondaryButtonClick: () => {
useDialog.getState().closeDialog();
onCancel && onCancel();
},
progressIndicatorProps,
choiceGroupProps,
textFieldProps,
primaryButtonDisabled,
}); });
} }

View File

@@ -30,7 +30,7 @@ import * as DataModels from "../../../Contracts/DataModels";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
const title = "Open Saved Queries"; const title: string = "Open Saved Queries";
export interface QueriesGridComponentProps { export interface QueriesGridComponentProps {
queriesClient: QueriesClient; queriesClient: QueriesClient;
@@ -196,9 +196,9 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
{ {
key: "Action", key: "Action",
name: "Action", name: "Action",
fieldName: undefined, fieldName: null,
minWidth: 70, minWidth: 70,
onRender: (query: Query) => { onRender: (query: Query, index: number, column: IColumn) => {
const buttonProps: IButtonProps = { const buttonProps: IButtonProps = {
iconProps: { iconProps: {
iconName: "More", iconName: "More",
@@ -214,15 +214,19 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
{ {
key: "Open", key: "Open",
text: "Open query", text: "Open query",
onClick: () => { onClick: (event: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>, menuItem: any) => {
this.props.onQuerySelect(query); this.props.onQuerySelect(query);
}, },
}, },
{ {
key: "Delete", key: "Delete",
text: "Delete query", text: "Delete query",
onClick: async () => { onClick: async (
event: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
menuItem: any
) => {
if (window.confirm("Are you sure you want to delete this query?")) { if (window.confirm("Are you sure you want to delete this query?")) {
const container = window.dataExplorer;
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteSavedQuery, { const startKey: number = TelemetryProcessor.traceStart(Action.DeleteSavedQuery, {
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: title, paneTitle: title,

View File

@@ -0,0 +1,16 @@
@import "../../../../less/Common/Constants.less";
.radioSwitchComponent {
cursor: pointer;
display: flex;
&>span:nth-child(n+2) {
margin-left: 10px;
}
.caption {
color: @BaseDark;
padding-left: @SmallSpace;
vertical-align: top;
}
}

View File

@@ -0,0 +1,51 @@
/**
* Horizontal switch component
*/
import { Icon } from "@fluentui/react";
import * as React from "react";
import { NormalizedEventKey } from "../../../Common/Constants";
import "./RadioSwitchComponent.less";
export interface Choice {
key: string;
onSelect: () => void;
label: string;
}
export interface RadioSwitchComponentProps {
choices: Choice[];
selectedKey: string;
onSelectionKeyChange?: (newValue: string) => void;
}
export class RadioSwitchComponent extends React.Component<RadioSwitchComponentProps> {
public render(): JSX.Element {
return (
<div className="radioSwitchComponent">
{this.props.choices.map((choice: Choice) => (
<span
tabIndex={0}
key={choice.key}
onClick={() => this.onSelect(choice)}
onKeyPress={(event) => this.onKeyPress(event, choice)}
>
<Icon iconName={this.props.selectedKey === choice.key ? "RadioBtnOn" : "RadioBtnOff"} />
<span className="caption">{choice.label}</span>
</span>
))}
</div>
);
}
private onSelect(choice: Choice): void {
this.props.onSelectionKeyChange && this.props.onSelectionKeyChange(choice.key);
choice.onSelect();
}
private onKeyPress(event: React.KeyboardEvent<HTMLSpanElement>, choice: Choice): void {
if (event.key === NormalizedEventKey.Enter || event.key === NormalizedEventKey.Space) {
this.onSelect(choice);
}
}
}

View File

@@ -0,0 +1,45 @@
/**
* Generic abstract React component that senses its dimensions.
* It updates its state and re-renders if dimensions change.
*/
import * as React from "react";
import * as ResizeSensor from "css-element-queries/src/ResizeSensor";
export abstract class ResizeSensorComponent<P, S> extends React.Component<P, S> {
private isSensing: boolean = false;
private resizeSensor: any;
public constructor(props: P) {
super(props);
}
protected abstract onDimensionsChanged(width: number, height: number): void;
protected abstract getSensorTarget(): HTMLElement;
public componentDidUpdate(): void {
if (this.isSensing) {
return;
}
const bar = this.getSensorTarget();
if (bar.clientWidth > 0 || bar.clientHeight > 0) {
const oldPosition = bar.style.position;
// TODO Find a better way to use constructor
this.resizeSensor = new (ResizeSensor as any)(bar, () => {
this.onDimensionsChanged(bar.clientWidth, bar.clientHeight);
});
this.isSensing = true;
// ResizeSensor.js sets position to 'relative' which makes the dropdown menu appear clipped.
// Undoing doesn't seem to affect resize sensing functionality.
bar.style.position = oldPosition;
}
}
public componentWillUnmount(): void {
if (!!this.resizeSensor) {
this.resizeSensor.detach();
}
}
}

View File

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

View File

@@ -14,10 +14,10 @@ import * as ViewModels from "../../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import { trace, traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor"; import { trace, traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types"; import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils"; import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter"; import Explorer from "../../Explorer";
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2"; import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
import "./SettingsComponent.less"; import "./SettingsComponent.less";
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils"; import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
@@ -121,6 +121,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
private collection: ViewModels.Collection; private collection: ViewModels.Collection;
private database: ViewModels.Database; private database: ViewModels.Database;
private offer: DataModels.Offer; private offer: DataModels.Offer;
private container: Explorer;
private changeFeedPolicyVisible: boolean; private changeFeedPolicyVisible: boolean;
private isFixedContainer: boolean; private isFixedContainer: boolean;
private shouldShowIndexingPolicyEditor: boolean; private shouldShowIndexingPolicyEditor: boolean;
@@ -132,6 +133,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.isCollectionSettingsTab = this.props.settingsTab.tabKind === ViewModels.CollectionTabKind.CollectionSettingsV2; this.isCollectionSettingsTab = this.props.settingsTab.tabKind === ViewModels.CollectionTabKind.CollectionSettingsV2;
if (this.isCollectionSettingsTab) { if (this.isCollectionSettingsTab) {
this.collection = this.props.settingsTab.collection as ViewModels.Collection; this.collection = this.props.settingsTab.collection as ViewModels.Collection;
this.container = this.collection?.container;
this.offer = this.collection?.offer(); this.offer = this.collection?.offer();
this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl(); this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl();
this.shouldShowIndexingPolicyEditor = userContext.apiType !== "Cassandra" && userContext.apiType !== "Mongo"; this.shouldShowIndexingPolicyEditor = userContext.apiType !== "Cassandra" && userContext.apiType !== "Mongo";
@@ -143,6 +145,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
userContext.apiType === "Mongo" && (!this.collection?.partitionKey || this.collection?.partitionKey.systemKey); userContext.apiType === "Mongo" && (!this.collection?.partitionKey || this.collection?.partitionKey.systemKey);
} else { } else {
this.database = this.props.settingsTab.database; this.database = this.props.settingsTab.database;
this.container = this.database?.container;
this.offer = this.database?.offer(); this.offer = this.database?.offer();
} }
@@ -219,13 +222,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.setAutoPilotStates(); this.setAutoPilotStates();
this.setBaseline(); this.setBaseline();
if (this.props.settingsTab.isActive()) { if (this.props.settingsTab.isActive()) {
useCommandBar.getState().setContextButtons(this.getTabsButtons()); this.props.settingsTab.getContainer().onUpdateTabsButtons(this.getTabsButtons());
} }
} }
componentDidUpdate(): void { componentDidUpdate(): void {
if (this.props.settingsTab.isActive()) { if (this.props.settingsTab.isActive()) {
useCommandBar.getState().setContextButtons(this.getTabsButtons()); this.props.settingsTab.getContainer().onUpdateTabsButtons(this.getTabsButtons());
} }
} }
@@ -290,7 +293,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.state.wasAutopilotOriginallySet !== this.state.isAutoPilotSelected; this.state.wasAutopilotOriginallySet !== this.state.isAutoPilotSelected;
public shouldShowKeyspaceSharedThroughputMessage = (): boolean => public shouldShowKeyspaceSharedThroughputMessage = (): boolean =>
userContext.apiType === "Cassandra" && hasDatabaseSharedThroughput(this.collection); this.container && userContext.apiType === "Cassandra" && hasDatabaseSharedThroughput(this.collection);
public hasConflictResolution = (): boolean => public hasConflictResolution = (): boolean =>
userContext?.databaseAccount?.properties?.enableMultipleWriteLocations && userContext?.databaseAccount?.properties?.enableMultipleWriteLocations &&
@@ -880,6 +883,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
const scaleComponentProps: ScaleComponentProps = { const scaleComponentProps: ScaleComponentProps = {
collection: this.collection, collection: this.collection,
database: this.database, database: this.database,
container: this.container,
isFixedContainer: this.isFixedContainer, isFixedContainer: this.isFixedContainer,
onThroughputChange: this.onThroughputChange, onThroughputChange: this.onThroughputChange,
throughput: this.state.throughput, throughput: this.state.throughput,
@@ -907,6 +911,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
const subSettingsComponentProps: SubSettingsComponentProps = { const subSettingsComponentProps: SubSettingsComponentProps = {
collection: this.collection, collection: this.collection,
container: this.container,
isAnalyticalStorageEnabled: this.isAnalyticalStorageEnabled, isAnalyticalStorageEnabled: this.isAnalyticalStorageEnabled,
changeFeedPolicyVisible: this.changeFeedPolicyVisible, changeFeedPolicyVisible: this.changeFeedPolicyVisible,
timeToLive: this.state.timeToLive, timeToLive: this.state.timeToLive,
@@ -959,6 +964,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = { const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
collection: this.collection, collection: this.collection,
container: this.container,
conflictResolutionPolicyMode: this.state.conflictResolutionPolicyMode, conflictResolutionPolicyMode: this.state.conflictResolutionPolicyMode,
conflictResolutionPolicyModeBaseline: this.state.conflictResolutionPolicyModeBaseline, conflictResolutionPolicyModeBaseline: this.state.conflictResolutionPolicyModeBaseline,
onConflictResolutionPolicyModeChange: this.onConflictResolutionPolicyModeChange, onConflictResolutionPolicyModeChange: this.onConflictResolutionPolicyModeChange,

View File

@@ -0,0 +1,14 @@
import ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { SettingsComponent, SettingsComponentProps } from "./SettingsComponent";
export class SettingsComponentAdapter implements ReactAdapter {
public parameters: ko.Computed<boolean>;
constructor(private props: SettingsComponentProps) {}
public renderComponent(): JSX.Element {
return this.parameters() ? <SettingsComponent {...this.props} /> : <></>;
}
}

View File

@@ -1,12 +1,13 @@
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import React from "react"; import React from "react";
import { ConflictResolutionComponentProps, ConflictResolutionComponent } from "./ConflictResolutionComponent";
import { container, collection } from "../TestUtils";
import * as DataModels from "../../../../Contracts/DataModels"; import * as DataModels from "../../../../Contracts/DataModels";
import { collection } from "../TestUtils";
import { ConflictResolutionComponent, ConflictResolutionComponentProps } from "./ConflictResolutionComponent";
describe("ConflictResolutionComponent", () => { describe("ConflictResolutionComponent", () => {
const baseProps: ConflictResolutionComponentProps = { const baseProps: ConflictResolutionComponentProps = {
collection: collection, collection: collection,
container: container,
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode.Custom, conflictResolutionPolicyMode: DataModels.ConflictResolutionMode.Custom,
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode.Custom, conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode.Custom,
onConflictResolutionPolicyModeChange: () => { onConflictResolutionPolicyModeChange: () => {

View File

@@ -1,19 +1,21 @@
import { ChoiceGroup, IChoiceGroupOption, ITextFieldProps, Stack, TextField } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import * as DataModels from "../../../../Contracts/DataModels";
import * as ViewModels from "../../../../Contracts/ViewModels"; import * as ViewModels from "../../../../Contracts/ViewModels";
import * as DataModels from "../../../../Contracts/DataModels";
import Explorer from "../../../Explorer";
import { import {
conflictResolutionCustomToolTip,
conflictResolutionLwwTooltip,
getChoiceGroupStyles,
getTextFieldStyles, getTextFieldStyles,
conflictResolutionLwwTooltip,
conflictResolutionCustomToolTip,
subComponentStackProps, subComponentStackProps,
getChoiceGroupStyles,
} from "../SettingsRenderUtils"; } from "../SettingsRenderUtils";
import { isDirty } from "../SettingsUtils"; import { TextField, ITextFieldProps, Stack, IChoiceGroupOption, ChoiceGroup } from "@fluentui/react";
import { ToolTipLabelComponent } from "./ToolTipLabelComponent"; import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
import { isDirty } from "../SettingsUtils";
export interface ConflictResolutionComponentProps { export interface ConflictResolutionComponentProps {
collection: ViewModels.Collection; collection: ViewModels.Collection;
container: Explorer;
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode; conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode; conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
onConflictResolutionPolicyModeChange: (newMode: DataModels.ConflictResolutionMode) => void; onConflictResolutionPolicyModeChange: (newMode: DataModels.ConflictResolutionMode) => void;

View File

@@ -1,12 +1,12 @@
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import React from "react"; import React from "react";
import * as DataModels from "../../../../Contracts/DataModels";
import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./IndexingPolicyComponent"; import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./IndexingPolicyComponent";
import * as DataModels from "../../../../Contracts/DataModels";
describe("IndexingPolicyComponent", () => { describe("IndexingPolicyComponent", () => {
const initialIndexingPolicyContent: DataModels.IndexingPolicy = { const initialIndexingPolicyContent: DataModels.IndexingPolicy = {
automatic: false, automatic: false,
indexingMode: "consistent", indexingMode: "",
includedPaths: [], includedPaths: [],
excludedPaths: [], excludedPaths: [],
}; };

View File

@@ -1,44 +1,44 @@
import * as React from "react";
import { import {
DetailsList, DetailsList,
DetailsListLayoutMode, DetailsListLayoutMode,
IColumn, Stack,
IconButton, IconButton,
Text,
SelectionMode,
IColumn,
MessageBar, MessageBar,
MessageBarType, MessageBarType,
SelectionMode,
Separator,
Spinner, Spinner,
SpinnerSize, SpinnerSize,
Stack, Separator,
Text,
} from "@fluentui/react"; } from "@fluentui/react";
import * as React from "react";
import { MongoIndex } from "../../../../../Utils/arm/generatedClients/cosmos/types";
import { CollapsibleSectionComponent } from "../../../CollapsiblePanel/CollapsibleSectionComponent";
import { import {
addMongoIndexStackProps, addMongoIndexStackProps,
createAndAddMongoIndexStackProps,
customDetailsListStyles, customDetailsListStyles,
mongoIndexingPolicyDisclaimer,
mediumWidthStackStyles,
subComponentStackProps,
createAndAddMongoIndexStackProps,
separatorStyles,
indexingPolicynUnsavedWarningMessage, indexingPolicynUnsavedWarningMessage,
infoAndToolTipTextStyle, infoAndToolTipTextStyle,
mediumWidthStackStyles,
mongoCompoundIndexNotSupportedMessage,
mongoIndexingPolicyDisclaimer,
onRenderRow, onRenderRow,
separatorStyles, mongoCompoundIndexNotSupportedMessage,
subComponentStackProps,
} from "../../SettingsRenderUtils"; } from "../../SettingsRenderUtils";
import { MongoIndex } from "../../../../../Utils/arm/generatedClients/2020-04-01/types";
import { import {
MongoIndexTypes,
AddMongoIndexProps, AddMongoIndexProps,
MongoIndexIdField,
MongoNotificationType,
getMongoIndexType, getMongoIndexType,
getMongoIndexTypeText, getMongoIndexTypeText,
isIndexTransforming, isIndexTransforming,
MongoIndexIdField,
MongoIndexTypes,
MongoNotificationType,
} from "../../SettingsUtils"; } from "../../SettingsUtils";
import { IndexingPolicyRefreshComponent } from "../IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
import { AddMongoIndexComponent } from "./AddMongoIndexComponent"; import { AddMongoIndexComponent } from "./AddMongoIndexComponent";
import { CollapsibleSectionComponent } from "../../../CollapsiblePanel/CollapsibleSectionComponent";
import { IndexingPolicyRefreshComponent } from "../IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
export interface MongoIndexingPolicyComponentProps { export interface MongoIndexingPolicyComponentProps {
mongoIndexes: MongoIndex[]; mongoIndexes: MongoIndex[];

View File

@@ -7,17 +7,20 @@ import * as SharedConstants from "../../../../Shared/Constants";
import { updateUserContext } from "../../../../UserContext"; import { updateUserContext } from "../../../../UserContext";
import Explorer from "../../../Explorer"; import Explorer from "../../../Explorer";
import { throughputUnit } from "../SettingsRenderUtils"; import { throughputUnit } from "../SettingsRenderUtils";
import { collection } from "../TestUtils"; import { collection, container } from "../TestUtils";
import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent"; import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent";
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component"; import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
describe("ScaleComponent", () => { describe("ScaleComponent", () => {
const nonNationalCloudContainer = new Explorer(); const nonNationalCloudContainer = new Explorer();
nonNationalCloudContainer.isRunningOnNationalCloud = () => false;
const targetThroughput = 6000; const targetThroughput = 6000;
const baseProps: ScaleComponentProps = { const baseProps: ScaleComponentProps = {
collection: collection, collection: collection,
database: undefined, database: undefined,
container: container,
isFixedContainer: false, isFixedContainer: false,
onThroughputChange: () => { onThroughputChange: () => {
return; return;
@@ -108,7 +111,7 @@ describe("ScaleComponent", () => {
let scaleComponent = new ScaleComponent(baseProps); let scaleComponent = new ScaleComponent(baseProps);
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)"); expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)");
let newProps = { ...baseProps }; let newProps = { ...baseProps, container: nonNationalCloudContainer };
scaleComponent = new ScaleComponent(newProps); scaleComponent = new ScaleComponent(newProps);
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)"); expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)");
@@ -121,7 +124,7 @@ describe("ScaleComponent", () => {
let scaleComponent = new ScaleComponent(baseProps); let scaleComponent = new ScaleComponent(baseProps);
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true); expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true);
const newProps = { ...baseProps }; const newProps = { ...baseProps, container: nonNationalCloudContainer };
scaleComponent = new ScaleComponent(newProps); scaleComponent = new ScaleComponent(newProps);
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true); expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true);
}); });

View File

@@ -7,7 +7,7 @@ import * as ViewModels from "../../../../Contracts/ViewModels";
import * as SharedConstants from "../../../../Shared/Constants"; import * as SharedConstants from "../../../../Shared/Constants";
import { userContext } from "../../../../UserContext"; import { userContext } from "../../../../UserContext";
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils"; import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
import { isRunningOnNationalCloud } from "../../../../Utils/CloudUtils"; import Explorer from "../../../Explorer";
import { import {
getTextFieldStyles, getTextFieldStyles,
getThroughputApplyLongDelayMessage, getThroughputApplyLongDelayMessage,
@@ -23,6 +23,7 @@ import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents
export interface ScaleComponentProps { export interface ScaleComponentProps {
collection: ViewModels.Collection; collection: ViewModels.Collection;
database: ViewModels.Database; database: ViewModels.Database;
container: Explorer;
isFixedContainer: boolean; isFixedContainer: boolean;
onThroughputChange: (newThroughput: number) => void; onThroughputChange: (newThroughput: number) => void;
throughput: number; throughput: number;
@@ -108,7 +109,11 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
}; };
public canThroughputExceedMaximumValue = (): boolean => { public canThroughputExceedMaximumValue = (): boolean => {
return !this.props.isFixedContainer && configContext.platform === Platform.Portal && !isRunningOnNationalCloud(); return (
!this.props.isFixedContainer &&
configContext.platform === Platform.Portal &&
!this.props.container.isRunningOnNationalCloud()
);
}; };
public getInitialNotificationElement = (): JSX.Element => { public getInitialNotificationElement = (): JSX.Element => {
@@ -197,12 +202,10 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
} }
private getFreeTierInfoMessage(): JSX.Element { private getFreeTierInfoMessage(): JSX.Element {
const freeTierLimits = SharedConstants.FreeTierLimits;
return ( return (
<Text> <Text>
With free tier, you will get the first {freeTierLimits.RU} RU/s and {freeTierLimits.Storage} GB of storage in With free tier, you will get the first 400 RU/s and 5 GB of storage in this account for free. To keep your
this account for free. To keep your account free, keep the total RU/s across all resources in the account to{" "} account free, keep the total RU/s across all resources in the account to 400 RU/s.
{freeTierLimits.RU} RU/s.
<Link <Link
href="https://docs.microsoft.com/en-us/azure/cosmos-db/understand-your-bill#billing-examples-with-free-tier-accounts" href="https://docs.microsoft.com/en-us/azure/cosmos-db/understand-your-bill#billing-examples-with-free-tier-accounts"
target="_blank" target="_blank"

View File

@@ -4,12 +4,14 @@ import { DatabaseAccount } from "../../../../Contracts/DataModels";
import { updateUserContext } from "../../../../UserContext"; import { updateUserContext } from "../../../../UserContext";
import Explorer from "../../../Explorer"; import Explorer from "../../../Explorer";
import { ChangeFeedPolicyState, GeospatialConfigType, TtlOff, TtlOn, TtlOnNoDefault, TtlType } from "../SettingsUtils"; import { ChangeFeedPolicyState, GeospatialConfigType, TtlOff, TtlOn, TtlOnNoDefault, TtlType } from "../SettingsUtils";
import { collection } from "../TestUtils"; import { collection, container } from "../TestUtils";
import { SubSettingsComponent, SubSettingsComponentProps } from "./SubSettingsComponent"; import { SubSettingsComponent, SubSettingsComponentProps } from "./SubSettingsComponent";
describe("SubSettingsComponent", () => { describe("SubSettingsComponent", () => {
const baseProps: SubSettingsComponentProps = { const baseProps: SubSettingsComponentProps = {
collection: collection, collection: collection,
container: container,
timeToLive: TtlType.On, timeToLive: TtlType.On,
timeToLiveBaseline: TtlType.On, timeToLiveBaseline: TtlType.On,
onTtlChange: () => { onTtlChange: () => {

View File

@@ -2,6 +2,7 @@ import { ChoiceGroup, IChoiceGroupOption, Label, Link, MessageBar, Stack, Text,
import * as React from "react"; import * as React from "react";
import * as ViewModels from "../../../../Contracts/ViewModels"; import * as ViewModels from "../../../../Contracts/ViewModels";
import { userContext } from "../../../../UserContext"; import { userContext } from "../../../../UserContext";
import Explorer from "../../../Explorer";
import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon"; import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
import { import {
changeFeedPolicyToolTip, changeFeedPolicyToolTip,
@@ -27,6 +28,8 @@ import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
export interface SubSettingsComponentProps { export interface SubSettingsComponentProps {
collection: ViewModels.Collection; collection: ViewModels.Collection;
container: Explorer;
timeToLive: TtlType; timeToLive: TtlType;
timeToLiveBaseline: TtlType; timeToLiveBaseline: TtlType;

View File

@@ -155,9 +155,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
this.state = { this.state = {
spendAckChecked: this.props.spendAckChecked, spendAckChecked: this.props.spendAckChecked,
exceedFreeTierThroughput: exceedFreeTierThroughput:
this.props.isFreeTierAccount && this.props.isFreeTierAccount && !this.props.isAutoPilotSelected && this.props.throughput > 400,
!this.props.isAutoPilotSelected &&
this.props.throughput > SharedConstants.FreeTierLimits.RU,
}; };
this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep; this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep;
@@ -443,9 +441,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
if (this.overrideWithAutoPilotSettings()) { if (this.overrideWithAutoPilotSettings()) {
this.props.onMaxAutoPilotThroughputChange(newThroughput); this.props.onMaxAutoPilotThroughputChange(newThroughput);
} else { } else {
this.setState({ this.setState({ exceedFreeTierThroughput: this.props.isFreeTierAccount && newThroughput > 400 });
exceedFreeTierThroughput: this.props.isFreeTierAccount && newThroughput > SharedConstants.FreeTierLimits.RU,
});
this.props.onThroughputChange(newThroughput); this.props.onThroughputChange(newThroughput);
} }
}; };
@@ -585,7 +581,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }} messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}
styles={messageBarStyles} styles={messageBarStyles}
> >
{`Billing will apply if you provision more than ${SharedConstants.FreeTierLimits.RU} RU/s of manual throughput, or if the resource scales beyond ${SharedConstants.FreeTierLimits.RU} RU/s with autoscale.`} {
"Billing will apply if you provision more than 400 RU/s of manual throughput, or if the resource scales beyond 400 RU/s with autoscale."
}
</MessageBar> </MessageBar>
)} )}
{this.props.getThroughputWarningMessage() && ( {this.props.getThroughputWarningMessage() && (

View File

@@ -36,6 +36,7 @@ describe("SettingsUtils", () => {
isDatabaseExpanded: ko.observable(false), isDatabaseExpanded: ko.observable(false),
isDatabaseShared: ko.computed(() => true), isDatabaseShared: ko.computed(() => true),
selectedSubnodeKind: ko.observable(undefined), selectedSubnodeKind: ko.observable(undefined),
selectDatabase: undefined,
expandDatabase: undefined, expandDatabase: undefined,
collapseDatabase: undefined, collapseDatabase: undefined,
loadCollections: undefined, loadCollections: undefined,

View File

@@ -1,7 +1,7 @@
import * as Constants from "../../../Common/Constants";
import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types"; import * as DataModels from "../../../Contracts/DataModels";
import * as Constants from "../../../Common/Constants";
import { MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
const zeroValue = 0; const zeroValue = 0;
export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy; export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy;

View File

@@ -1,7 +1,7 @@
import ko from "knockout";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import ko from "knockout";
export const container = new Explorer(); export const container = new Explorer();
@@ -13,7 +13,7 @@ export const collection = ({
analyticalStorageTtl: ko.observable<number>(undefined), analyticalStorageTtl: ko.observable<number>(undefined),
indexingPolicy: ko.observable<DataModels.IndexingPolicy>({ indexingPolicy: ko.observable<DataModels.IndexingPolicy>({
automatic: true, automatic: true,
indexingMode: "consistent", indexingMode: "default",
includedPaths: [], includedPaths: [],
excludedPaths: [], excludedPaths: [],
}), }),

View File

@@ -58,7 +58,7 @@ export class TabComponent extends React.Component<TabComponentProps> {
as="span" as="span"
className={className} className={className}
role="presentation" role="presentation"
onActivated={() => this.setActiveTab(index)} onActivated={(e) => this.setActiveTab(index)}
aria-label={`Select tab: ${tab.title}`} aria-label={`Select tab: ${tab.title}`}
> >
{tab.title} {tab.title}

View File

@@ -4,7 +4,7 @@ import { ThroughputInput } from "./ThroughputInput";
const props = { const props = {
isDatabase: false, isDatabase: false,
showFreeTierExceedThroughputTooltip: true, showFreeTierExceedThroughputTooltip: true,
isSharded: true, isSharded: false,
setThroughputValue: () => jest.fn(), setThroughputValue: () => jest.fn(),
setIsAutoscale: () => jest.fn(), setIsAutoscale: () => jest.fn(),
onCostAcknowledgeChange: () => jest.fn(), onCostAcknowledgeChange: () => jest.fn(),

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